From 0abbf06225099c8b0a4badefc726a3c5ba074b10 Mon Sep 17 00:00:00 2001 From: "Phyks (Lucas Verney)" Date: Wed, 28 Sep 2016 15:38:55 -0400 Subject: [PATCH] First demo of JSON export for Amazon --- .gitignore | 1 + TODO | 2 ++ capabilities/__init__.py | 0 capabilities/base.py | 14 ++++++++ capabilities/bill.py | 21 ++++++++++++ cozyweboob.py | 73 ++++++++++++++++++++++++++++++++++++++++ tools/__init__.py | 0 tools/jsonwriter.py | 27 +++++++++++++++ 8 files changed, 138 insertions(+) create mode 100644 .gitignore create mode 100644 TODO create mode 100644 capabilities/__init__.py create mode 100644 capabilities/base.py create mode 100644 capabilities/bill.py create mode 100755 cozyweboob.py create mode 100644 tools/__init__.py create mode 100644 tools/jsonwriter.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/TODO b/TODO new file mode 100644 index 0000000..68477a7 --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +* history (Detail) vs Bill? +* _url ? diff --git a/capabilities/__init__.py b/capabilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/capabilities/base.py b/capabilities/base.py new file mode 100644 index 0000000..7968505 --- /dev/null +++ b/capabilities/base.py @@ -0,0 +1,14 @@ +from weboob.capabilities.base import empty + + +def clean_object(o): + """ + Returns a JSON-serializable dict from a Weboob object. + """ + o = o.to_dict() + for k, v in o.items(): + if empty(v): + # Replace empty values by None, avoid "NotLoaded is not + # serializable" error + o[k] = None + return o diff --git a/capabilities/bill.py b/capabilities/bill.py new file mode 100644 index 0000000..e6d8431 --- /dev/null +++ b/capabilities/bill.py @@ -0,0 +1,21 @@ +from base import clean_object + + +def to_cozy(document): + """ + Export a CapDocument object to JSON, to pass it to Cozy instance. + """ + # Fetch the list of subscriptions + subscriptions = list(document.iter_subscription()) + # Return a formatted dict with all the infos + return { + "subscriptions": [ # List of subscriptions + clean_object(subscription) for subscription in subscriptions + ], + "bills": { # List of bills for each subscription + subscription.id: [ + clean_object(bill) for bill in document.iter_bills(subscription) + ] + for subscription in subscriptions + } + } diff --git a/cozyweboob.py b/cozyweboob.py new file mode 100755 index 0000000..5544a60 --- /dev/null +++ b/cozyweboob.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python2 +from __future__ import print_function + +import getpass + +from weboob.core import Weboob + +from capabilities import bill +from tools.jsonwriter import pretty_json + + +class Connector(object): + """ + Connector is a tool that connects to common websites like bank website, + phone operator website... and that grabs personal data from there. + Credentials are required to make this operation. + + Technically, connectors are weboob backend wrappers. + """ + + @staticmethod + def version(): + return Weboob.VERSION + + def update(self): + return self.weboob.update() + + def __init__(self, modulename, parameters): + """ + Create a Weboob handle and try to load the modules. + """ + self.weboob = Weboob() + + # Careful: this is extracted from weboob's code. + # Install the module if necessary and hide the progress. + class DummyProgress: + def progress(self, a, b): + pass + + repositories = self.weboob.repositories + minfo = repositories.get_module_info(modulename) + if minfo is not None and not minfo.is_installed(): + repositories.install(minfo, progress=DummyProgress()) + + # Calls the backend. + self.backend = self.weboob.build_backend(modulename, parameters) + + +def main(email, password=None): + """ + Main code + """ + if password is None: + # Ask for password if not provided + password = getpass.getpass("Password? ") + + connector = Connector( + "amazon", + { + "website": "www.amazon.fr", + "email": email, + "password": password + } + ) + return bill.to_cozy(connector.backend) + + +if __name__ == '__main__': + print( + pretty_json( + main(raw_input("Email? ")) + ) + ) diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/jsonwriter.py b/tools/jsonwriter.py new file mode 100644 index 0000000..e16d04b --- /dev/null +++ b/tools/jsonwriter.py @@ -0,0 +1,27 @@ +import json + +from datetime import datetime +from decimal import Decimal + + +class CustomJSONEncoder(json.JSONEncoder): + """ + Custom JSONEncoder to support more types. + """ + def default(self, o): + if isinstance(o, datetime): + # Serialize datetime objects to ISO dates + return o.isoformat() + elif isinstance(o, Decimal): + # Serialize Decimal objects to string + return str(o) + return json.JSONEncoder.default(self, o) + + +def pretty_json(foo): + """ + Pretty printing of JSON output, using the custom JSONEncoder. + """ + return json.dumps(foo, sort_keys=True, + indent=4, separators=(',', ': '), + cls=CustomJSONEncoder)