From 36d91dd0b398b96222bf4b1fa3b91e47887017cd Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Fri, 20 Dec 2013 16:00:09 +0100 Subject: [PATCH] [docs] Changed the theme to the Flask theme I like it because it doesn't allow the text to fill horizontally over the entire screen. --- LICENSE | 17 +- doc/build/html/.buildinfo | 2 +- doc/build/html/README.html | 64 +- doc/build/html/_modules/accounting.html | 418 ------------- .../html/_modules/accounting/client.html | 102 ++-- .../html/_modules/accounting/decorators.html | 119 +++- .../html/_modules/accounting/exceptions.html | 53 +- .../html/_modules/accounting/gtkclient.html | 88 +-- .../html/_modules/accounting/models.html | 62 +- .../html/_modules/accounting/storage.html | 112 ++-- .../accounting/storage/ledgercli.html | 193 +++++- .../html/_modules/accounting/storage/sql.html | 65 +- .../accounting/storage/sql/models.html | 57 +- .../html/_modules/accounting/transport.html | 60 +- doc/build/html/_modules/accounting/web.html | 219 +++---- doc/build/html/_modules/index.html | 45 +- doc/build/html/_sources/api/asyncio-test.txt | 7 - doc/build/html/_sources/index.txt | 6 +- doc/build/html/_static/Open_book_nae_02.svg | 55 ++ .../html/_static/accounting-api-logo.png | Bin 0 -> 9023 bytes .../html/_static/accounting-api-logo.svg | 90 +++ doc/build/html/_static/default.css | 256 -------- doc/build/html/_static/flasky.css | 571 +++++++++++++++++ doc/build/html/_static/pydoctheme.css | 170 ------ doc/build/html/_static/sidebar.js | 159 ----- doc/build/html/api/accounting.html | 140 +++-- doc/build/html/api/accounting.storage.html | 85 +-- .../html/api/accounting.storage.sql.html | 63 +- doc/build/html/api/asyncio-test.html | 96 --- doc/build/html/api/modules.html | 48 +- doc/build/html/genindex.html | 72 ++- doc/build/html/http-routingtable.html | 46 +- doc/build/html/index.html | 75 +-- doc/build/html/objects.inv | Bin 1206 -> 1236 bytes doc/build/html/py-modindex.html | 42 +- doc/build/html/restapi.html | 61 +- doc/build/html/search.html | 40 +- doc/build/html/searchindex.js | 2 +- doc/source/_static/Open_book_nae_02.svg | 55 ++ doc/source/_static/accounting-api-logo.png | Bin 0 -> 9023 bytes doc/source/_static/accounting-api-logo.svg | 90 +++ doc/source/_themes/LICENSE | 40 ++ doc/source/_themes/flask/layout.html | 24 + doc/source/_themes/flask/relations.html | 19 + doc/source/_themes/flask/static/flasky.css_t | 577 ++++++++++++++++++ doc/source/_themes/flask/theme.conf | 9 + doc/source/_themes/flask_theme_support.py | 86 +++ .../_themes/pydoctheme/static/pydoctheme.css | 170 ------ doc/source/_themes/pydoctheme/theme.conf | 23 - doc/source/conf.py | 3 +- doc/source/index.rst | 6 +- 51 files changed, 2744 insertions(+), 2118 deletions(-) delete mode 100644 doc/build/html/_modules/accounting.html delete mode 100644 doc/build/html/_sources/api/asyncio-test.txt create mode 100644 doc/build/html/_static/Open_book_nae_02.svg create mode 100644 doc/build/html/_static/accounting-api-logo.png create mode 100644 doc/build/html/_static/accounting-api-logo.svg delete mode 100644 doc/build/html/_static/default.css create mode 100644 doc/build/html/_static/flasky.css delete mode 100644 doc/build/html/_static/pydoctheme.css delete mode 100644 doc/build/html/_static/sidebar.js delete mode 100644 doc/build/html/api/asyncio-test.html create mode 100644 doc/source/_static/Open_book_nae_02.svg create mode 100644 doc/source/_static/accounting-api-logo.png create mode 100644 doc/source/_static/accounting-api-logo.svg create mode 100644 doc/source/_themes/LICENSE create mode 100644 doc/source/_themes/flask/layout.html create mode 100644 doc/source/_themes/flask/relations.html create mode 100644 doc/source/_themes/flask/static/flasky.css_t create mode 100644 doc/source/_themes/flask/theme.conf create mode 100644 doc/source/_themes/flask_theme_support.py delete mode 100644 doc/source/_themes/pydoctheme/static/pydoctheme.css delete mode 100644 doc/source/_themes/pydoctheme/theme.conf diff --git a/LICENSE b/LICENSE index e78a705..9ea8641 100644 --- a/LICENSE +++ b/LICENSE @@ -35,15 +35,24 @@ are, unless specified as other by the file's contents, generated by sphinx full license can be found in SPHINX-LICENSE. -Python 3 Sphinx Theme ---------------------- +Flask Sphinx Theme +------------------ The files (an asterisk "*" indicates wildcard substitution) doc/source/_themes/ -are from the python 3 sphinx documentation source code and are licensed under -the PSF. For the full license, see the file PSF-PY3.3-LICENSE. +are from the flask-sphinx-themes project by Armin Ronacher and are licensed +under the 3-clase BSD license. The full license can be found in +doc/source/_themes/LICENSE. + + +accounting-api logo +------------------- + +The file doc/source/_static/accounting-api-logo.svg is from the Open Clip Art +Library and is released under the public domain (CC0 1.0). The color has been +changed to better fit the theme of the documentation. Bootstrap, AngularJS and jQuery diff --git a/doc/build/html/.buildinfo b/doc/build/html/.buildinfo index 71ce1e1..222f238 100644 --- a/doc/build/html/.buildinfo +++ b/doc/build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 8b75ea2eddb26034f4ae6f4f86a1f854 +config: 552d0a651eb779240d5654552064830f tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/doc/build/html/README.html b/doc/build/html/README.html index 2f01152..41ae220 100644 --- a/doc/build/html/README.html +++ b/doc/build/html/README.html @@ -8,7 +8,7 @@ The Accounting API — Accounting API 0.1-beta documentation - + - + + + + + + + +
+

Table Of Contents

- -

Previous topic

-

Welcome to Accounting API’s documentation!

-

Next topic

-

REST API

+

Related Topics

+

This Page

- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting.html b/doc/build/html/_modules/accounting.html deleted file mode 100644 index 4a5fca6..0000000 --- a/doc/build/html/_modules/accounting.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - - accounting — Accounting API 0.1-beta documentation - - - - - - - - - - - - - - -
-
-
-
- -

Source code for accounting

-import sys
-import subprocess
-import logging
-import time
-
-from datetime import datetime
-from xml.etree import ElementTree
-from contextlib import contextmanager
-
-from accounting.models import Account, Transaction, Posting, Amount
-
-_log = logging.getLogger(__name__)
-
-
[docs]class Ledger: - def __init__(self, ledger_file=None, ledger_bin=None): - if ledger_file is None: - raise ValueError('ledger_file cannot be None') - - self.ledger_bin = ledger_bin or 'ledger' - self.ledger_file = ledger_file - _log.info('ledger file: %s', ledger_file) - - self.locked = False - self.ledger_process = None - - @contextmanager -
[docs] def locked_process(self): - r''' - Context manager that checks that the ledger process is not already - locked, then "locks" the process and yields the process handle and - unlocks the process when execution is returned. - - Since this decorated as a :func:`contextlib.contextmanager` the - recommended use is with the ``with``-statement. - - .. code-block:: python - - with self.locked_process() as p: - p.stdin.write(b'bal\n') - - output = self.read_until_prompt(p) - - ''' - if self.locked: - raise RuntimeError('The process has already been locked,' - ' something\'s out of order.') - - # XXX: This code has no purpose in a single-threaded process - timeout = 5 # Seconds - - for i in range(1, timeout + 2): - if i > timeout: - raise RuntimeError('Ledger process is already locked') - - if not self.locked: - break - else: - _log.info('Waiting for one second... %d/%d', i, timeout) - time.sleep(1) - - process = self.get_process() - - self.locked = True - _log.debug('Lock enabled') - - yield process - - self.locked = False - _log.debug('Lock disabled') -
-
[docs] def assemble_arguments(self): - ''' - Returns a list of arguments suitable for :class:`subprocess.Popen` based on - :attr:`self.ledger_bin` and :attr:`self.ledger_file`. - ''' - return [ - self.ledger_bin, - '-f', - self.ledger_file, - ] -
-
[docs] def init_process(self): - ''' - Creates a new (presumably) ledger subprocess based on the args from - :meth:`Ledger.assemble_arguments()` and then runs - :meth:`Ledger.read_until_prompt()` once (which should return the banner - text) and discards the output. - ''' - _log.debug('Starting ledger process...') - self.ledger_process = subprocess.Popen( - self.assemble_arguments(), - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE) - - # Swallow the banner - with self.locked_process() as p: - self.read_until_prompt(p) - - return self.ledger_process -
-
[docs] def get_process(self): - ''' - Returns :attr:`self.ledger_process` if it evaluates to ``True``. If - :attr:`self.ledger_process` is not set the result of - :meth:`self.init_process() <Ledger.init_process>` is returned. - ''' - return self.ledger_process or self.init_process() -
-
[docs] def read_until_prompt(self, process): - r''' - Reads from the subprocess instance :data:`process` until it finds a - combination of ``\n]\x20`` (the prompt), then returns the output - without the prompt. - ''' - output = b'' - - while True: - line = process.stdout.read(1) # XXX: This is a hack - - output += line - - if b'\n] ' in output: - _log.debug('Found prompt!') - break - - output = output[:-3] # Cut away the prompt - - _log.debug('output: %s', output) - - return output -
-
[docs] def send_command(self, command): - output = None - - with self.locked_process() as p: - if isinstance(command, str): - command = command.encode('utf8') - - p.stdin.write(command + b'\n') - p.stdin.flush() - - output = self.read_until_prompt(p) - - self.ledger_process.send_signal(subprocess.signal.SIGTERM) - _log.debug('Waiting for ledger to shut down') - self.ledger_process.wait() - self.ledger_process = None - - return output -
-
[docs] def add_transaction(self, transaction): - ''' - Writes a transaction to the ledger file by opening it in 'ab' mode and - writing a ledger transaction based on the - :class:`~accounting.models.Transaction` instance in :data:`transaction`. - ''' - if not transaction.metadata.get('Id'): - transaction.generate_id() - - transaction_template = ('\n{date} {t.payee}\n' - '{tags}' - '{postings}') - - metadata_template = ' ;{0}: {1}\n' - - # TODO: Generate metadata for postings - posting_template = (' {account} {p.amount.symbol}' - ' {p.amount.amount}\n') - - output = b'' - - # XXX: Even I hardly understands what this does, however I indent it it - # stays unreadable. - output += transaction_template.format( - date=transaction.date.strftime('%Y-%m-%d'), - t=transaction, - tags=''.join([ - metadata_template.format(k, v) \ - for k, v in transaction.metadata.items()]), - postings=''.join([posting_template.format( - p=p, - account=p.account + ' ' * ( - 80 - (len(p.account) + len(p.amount.symbol) + - len(str(p.amount.amount)) + 1 + 2) - )) for p in transaction.postings - ]) - ).encode('utf8') - - with open(self.ledger_file, 'ab') as f: - f.write(output) - - _log.debug('written to file: %s', output) -
-
[docs] def bal(self): - output = self.send_command('xml') - - if output is None: - raise RuntimeError('bal call returned no output') - - accounts = [] - - xml = ElementTree.fromstring(output.decode('utf8')) - - accounts = self._recurse_accounts(xml.find('./accounts')) - - return accounts -
- def _recurse_accounts(self, root): - accounts = [] - - for account in root.findall('./account'): - name = account.find('./fullname').text - - amounts = [] - - # Try to find an account total value, then try to find the account - # balance - account_amounts = account.findall( - './account-total/balance/amount') or \ - account.findall('./account-amount/amount') or \ - account.findall('./account-total/amount') - - if account_amounts: - for amount in account_amounts: - quantity = amount.find('./quantity').text - symbol = amount.find('./commodity/symbol').text - - amounts.append(Amount(amount=quantity, symbol=symbol)) - else: - _log.warning('Account %s does not have any amounts', name) - - accounts.append(Account(name=name, - amounts=amounts, - accounts=self._recurse_accounts(account))) - - return accounts - -
[docs] def reg(self): - output = self.send_command('xml') - - if output is None: - raise RuntimeError('reg call returned no output') - - entries = [] - - reg_xml = ElementTree.fromstring(output.decode('utf8')) - - for transaction in reg_xml.findall('./transactions/transaction'): - date = datetime.strptime(transaction.find('./date').text, - '%Y/%m/%d') - payee = transaction.find('./payee').text - - postings = [] - - for posting in transaction.findall('./postings/posting'): - account = posting.find('./account/name').text - amount = posting.find('./post-amount/amount/quantity').text - symbol = posting.find( - './post-amount/amount/commodity/symbol').text - - # Get the posting metadata - metadata = {} - - values = posting.findall('./metadata/value') - if values: - for value in values: - key = value.get('key') - value = value.find('./string').text - - _log.debug('metadata: %s: %s', key, value) - - metadata.update({key: value}) - - postings.append( - Posting(account=account, - metadata=metadata, - amount=Amount(amount=amount, symbol=symbol))) - - # Get the transaction metadata - metadata = {} - - values = transaction.findall('./metadata/value') - if values: - for value in values: - key = value.get('key') - value = value.find('./string').text - - _log.debug('metadata: %s: %s', key, value) - - metadata.update({key: value}) - - # Add a Transaction instance to the list - entries.append( - Transaction(date=date, payee=payee, postings=postings, - metadata=metadata)) - - return entries - -
-
[docs]def main(argv=None): - import argparse - if argv is None: - argv = sys.argv - - parser = argparse.ArgumentParser() - parser.add_argument('-v', '--verbosity', - default='INFO', - help=('Filter logging output. Possible values:' + - ' CRITICAL, ERROR, WARNING, INFO, DEBUG')) - - args = parser.parse_args(argv[1:]) - logging.basicConfig(level=getattr(logging, args.verbosity, 'INFO')) - ledger = Ledger(ledger_file='non-profit-test-data.ledger') - print(ledger.bal()) - print(ledger.reg()) - -
-if __name__ == '__main__': - sys.exit(main()) -
- -
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/client.html b/doc/build/html/_modules/accounting/client.html index 0d05bbc..8f07164 100644 --- a/doc/build/html/_modules/accounting/client.html +++ b/doc/build/html/_modules/accounting/client.html @@ -8,7 +8,7 @@ accounting.client — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -51,10 +57,15 @@

Source code for accounting.client

+# Part of accounting-api project:
+# https://gitorious.org/conservancy/accounting-api
+# License: AGPLv3-or-later
+
 import sys
 import argparse
 import json
 import logging
+import locale
 
 from datetime import datetime
 from decimal import Decimal
@@ -64,6 +75,8 @@
 from accounting.models import Transaction, Posting, Amount
 from accounting.transport import AccountingDecoder, AccountingEncoder
 
+locale.setlocale(locale.LC_ALL, '')
+
 _log = logging.getLogger(__name__)
 
 
@@ -96,24 +109,29 @@
 
         return self._decode_response(requests.post(self.host + path, **kw))
 
-
[docs] def simple_transaction(self, from_acc, to_acc, amount): +
[docs] def simple_transaction(self, from_acc, to_acc, amount, symbol=None, + payee=None): + if symbol is None: + # Get the currency from the environment locale + symbol = locale.localeconv()['int_curr_symbol'].strip() + t = Transaction( date=datetime.today(), - payee='PayPal donation', + payee=payee, postings=[ Posting(account=from_acc, - amount=Amount(symbol='$', amount=-amount)), + amount=Amount(symbol=symbol, amount=-amount)), Posting(account=to_acc, - amount=Amount(symbol='$', amount=amount)) + amount=Amount(symbol=symbol, amount=amount)) ] ) return self.post('/transaction', {'transactions': [t]})
[docs] def get_register(self): - register = self.get('/register') + register = self.get('/transaction') - return register['register_report'] + return register['transactions']
[docs]def main(argv=None, prog=None): @@ -150,18 +171,25 @@ insert = actions.add_parser('insert', aliases=['in']) + insert.add_argument('payee', + help='The payee line of the transaction') insert.add_argument('from_account') insert.add_argument('to_account') - insert.add_argument('amount', type=Decimal) + insert.add_argument('amount', type=Decimal, + help='The amount deducted from from_account and added' + ' to to_account') + insert.add_argument('-s', '--symbol', + help='The symbol for the amount, e.g. $ or USD for' + ' USD. Defaults to your locale\'s setting.') - balance = actions.add_parser('balance', aliases=['bal']) + actions.add_parser('balance', aliases=['bal']) - register = actions.add_parser('register', aliases=['reg']) + actions.add_parser('register', aliases=['reg']) parser.add_argument('-v', '--verbosity', default='WARNING', help=('Filter logging output. Possible values:' + - ' CRITICAL, ERROR, WARNING, INFO, DEBUG')) + ' CRITICAL, ERROR, WARNING, INFO, DEBUG')) parser.add_argument('--host', default='http://localhost:5000') args = parser.parse_args(argv) @@ -172,7 +200,8 @@ if args.action in ['insert', 'in']: print(client.simple_transaction(args.from_account, args.to_account, - args.amount)) + args.amount, payee=args.payee, + symbol=args.symbol)) elif args.action in ['balance', 'bal']: print_balance_accounts(client.get_balance()) elif args.action in ['register', 'reg']: @@ -189,6 +218,15 @@
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/decorators.html b/doc/build/html/_modules/accounting/decorators.html index d460479..67538ca 100644 --- a/doc/build/html/_modules/accounting/decorators.html +++ b/doc/build/html/_modules/accounting/decorators.html @@ -8,7 +8,7 @@ accounting.decorators — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -51,9 +57,13 @@

Source code for accounting.decorators

+# Part of accounting-api project:
+# https://gitorious.org/conservancy/accounting-api
+# License: AGPLv3-or-later
+
 from functools import wraps
 
-from flask import jsonify
+from flask import jsonify, request
 
 from accounting.exceptions import AccountingException
 
@@ -70,7 +80,69 @@
         except AccountingException as exc:
             return jsonify(error=exc)
 
-    return wrapper
+ return wrapper + +
+
[docs]def cors(origin_callback=None): + ''' + Flask endpoint decorator. + + Example: + + .. code-block:: python + + @app.route('/cors-endpoint', methods=['GET', 'OPTIONS']) + @cors() + def cors_endpoint(): + return jsonify(message='This is accessible via a cross-origin XHR') + + # Or if you want to control the domains this resource can be requested + # from via CORS: + domains = ['http://wandborg.se', 'http://sfconservancy.org'] + + def restrict_domains(origin): + return ' '.join(domains) + + @app.route('/restricted-cors-endpoint') + @cors(restrict_domains) + def restricted_cors_endpoint(): + return jsonify( + message='This is accessible from %s' % ', '.join(domains)) + + :param function origin_callback: A callback that takes one str() argument + containing the ``Origin`` HTTP header from the :data:`request` object. + This can be used to filter out which domains the resource can be + requested via CORS from. + ''' + if origin_callback is None: + origin_callback = allow_all_origins + + def decorator(func): + @wraps(func) + def wrapper(*args, **kw): + response = func(*args, **kw) + cors_headers = { + 'Access-Control-Allow-Origin': + origin_callback(request.headers.get('Origin')) or '*', + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Max-Age': 3600, + 'Access-Control-Allow-Methods': 'POST, GET, DELETE', + 'Access-Control-Allow-Headers': + 'Accept, Content-Type, Connection, Cookie' + } + + for key, val in cors_headers.items(): + response.headers[key] = val + + return response + + return wrapper + + return decorator + +
+
[docs]def allow_all_origins(origin): + return origin
@@ -78,6 +150,15 @@
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/exceptions.html b/doc/build/html/_modules/accounting/exceptions.html index 30788e1..85f1631 100644 --- a/doc/build/html/_modules/accounting/exceptions.html +++ b/doc/build/html/_modules/accounting/exceptions.html @@ -8,7 +8,7 @@ accounting.exceptions — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -51,6 +57,10 @@

Source code for accounting.exceptions

+# Part of accounting-api project:
+# https://gitorious.org/conservancy/accounting-api
+# License: AGPLv3-or-later
+
 
[docs]class AccountingException(Exception): ''' Used as a base for exceptions that are returned to the caller via the @@ -64,6 +74,15 @@
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/gtkclient.html b/doc/build/html/_modules/accounting/gtkclient.html index eb2e87a..b1a520a 100644 --- a/doc/build/html/_modules/accounting/gtkclient.html +++ b/doc/build/html/_modules/accounting/gtkclient.html @@ -8,7 +8,7 @@ accounting.gtkclient — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -51,6 +57,10 @@

Source code for accounting.gtkclient

+# Part of accounting-api project:
+# https://gitorious.org/conservancy/accounting-api
+# License: AGPLv3-or-later
+
 import sys
 import logging
 import threading
@@ -74,7 +84,7 @@
     def decorator(func):
         @wraps(func)
         def wrapper(self, *args, **kw):
-            self.status_description.set_text(description)
+            self.activity_description.set_text(description)
             self.activity_indicator.show()
             self.activity_indicator.start()
 
@@ -93,7 +103,7 @@
 
[docs]def indicate_activity_done(func): @wraps(func) def wrapper(self, *args, **kw): - self.status_description.set_text('') + self.activity_description.set_text('') self.activity_indicator.stop() self.activity_indicator.hide() @@ -111,13 +121,14 @@ self.load_ui(pkg_resources.resource_filename( 'accounting', 'res/client-ui.glade')) - self.aboutdialog.set_transient_for(self.accounting_window) + self.about_dialog.set_transient_for(self.accounting_window) self.accounting_window.connect('delete-event', Gtk.main_quit) self.accounting_window.set_border_width(0) self.accounting_window.set_default_geometry(640, 360) self.accounting_window.show_all() + self.transaction_detail.hide()
[docs] def load_ui(self, path): _log.debug('Loading UI...') @@ -126,12 +137,18 @@ builder.connect_signals(self) for element in builder.get_objects(): - _log.debug('Loaded %s', Gtk.Buildable.get_name(element)) - setattr(self, Gtk.Buildable.get_name(element), element) + try: + setattr(self, Gtk.Buildable.get_name(element), element) + _log.debug('Loaded %s', Gtk.Buildable.get_name(element)) + except TypeError as exc: + _log.error('%s could not be loaded: %s', element, exc) _log.debug('UI loaded')
-
[docs] def on_transaction_selected(self, widget): +
[docs] def on_transaction_new_activate(self, widget): + self.transaction_edit_window.show_all() +
+
[docs] def on_transaction_view_cursor_changed(self, widget): selection = self.transaction_view.get_selection() selection.set_mode(Gtk.SelectionMode.SINGLE) xact_store, xact_iter = selection.get_selected() @@ -141,7 +158,7 @@ for transaction in self.transaction_data: if transaction.id == xact_id: - self.lbl_payee.set_text(transaction.payee) + self.transaction_header.set_text(transaction.payee) self.posting_store.clear() @@ -152,19 +169,22 @@ posting.amount.symbol ]) - self.detail_view.show() + self.transaction_detail.show() break
[docs] def on_show_about_activate(self, widget): _log.debug('Showing About') - self.aboutdialog.show_all() + self.about_dialog.show_all()
-
[docs] def on_aboutdialog_close(self, widget): +
[docs] def on_about_dialog_response(self, widget, response_type): _log.debug('Closing About') - self.aboutdialog.hide_all() + if response_type == Gtk.ResponseType.CANCEL: + self.about_dialog.hide() + else: + _log.error('Unexpected response_type: %d', response_type)
@indicate_activity('Refreshing Transactions') -
[docs] def on_refresh_transactions_activate(self, widget): +
[docs] def on_transaction_refresh_activate(self, widget): def load_transactions(): transactions = self.client.get_register() GLib.idle_add(self.on_transactions_loaded, transactions) @@ -192,7 +212,6 @@ GObject.threads_init() accounting = AccountingApplication() - #accounting_win.connect('delete-event', Gtk.main_quit) Gtk.main()
@@ -205,6 +224,15 @@
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/models.html b/doc/build/html/_modules/accounting/models.html index 9feba99..16c4396 100644 --- a/doc/build/html/_modules/accounting/models.html +++ b/doc/build/html/_modules/accounting/models.html @@ -8,7 +8,7 @@ accounting.models — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -51,13 +57,18 @@

Source code for accounting.models

+# Part of accounting-api project:
+# https://gitorious.org/conservancy/accounting-api
+# License: AGPLv3-or-later
+
 import uuid
 from decimal import Decimal
 
 
 
[docs]class Transaction: - def __init__(self, date=None, payee=None, postings=None, metadata=None, - _generate_id=False): + def __init__(self, id=None, date=None, payee=None, postings=None, + metadata=None, _generate_id=False): + self.id = id self.date = date self.payee = payee self.postings = postings @@ -67,10 +78,10 @@ self.generate_id()
[docs] def generate_id(self): - self.metadata.update({'Id': uuid.uuid4()}) + self.id = str(uuid.uuid4())
def __repr__(self): - return ('<{self.__class__.__name__} {date}' + + return ('<{self.__class__.__name__} {self.id} {date}' + ' {self.payee} {self.postings}').format( self=self, date=self.date.strftime('%Y-%m-%d')) @@ -113,6 +124,15 @@
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/storage.html b/doc/build/html/_modules/accounting/storage.html index cca698f..fdca483 100644 --- a/doc/build/html/_modules/accounting/storage.html +++ b/doc/build/html/_modules/accounting/storage.html @@ -8,7 +8,7 @@ accounting.storage — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -51,31 +57,75 @@

Source code for accounting.storage

-class Storage:
-
[docs] ''' +# Part of accounting-api project: +# https://gitorious.org/conservancy/accounting-api +# License: AGPLv3-or-later + +from abc import ABCMeta, abstractmethod + +from accounting.exceptions import AccountingException + + +
[docs]class Storage: + ''' ABC for accounting storage ''' + __metaclass__ = ABCMeta + def __init__(self, *args, **kw): - raise NotImplementedError() + pass - def get_transactions(self, *args, **kw): -
[docs] raise NotImplementedError() + @abstractmethod +
[docs] def get_transactions(self, *args, **kw): + raise NotImplementedError +
+ @abstractmethod +
[docs] def get_transaction(self, *args, **kw): + raise NotImplementedError +
+ @abstractmethod +
[docs] def get_account(self, *args, **kw): + raise NotImplementedError +
+ @abstractmethod +
[docs] def get_accounts(self, *args, **kw): + raise NotImplementedError +
+ @abstractmethod +
[docs] def add_transaction(self, transaction): + raise NotImplementedError +
+ @abstractmethod +
[docs] def update_transaction(self, transaction): + raise NotImplementedError +
+ @abstractmethod +
[docs] def delete_transaction(self, transaction_id): + raise NotImplementedError +
+ @abstractmethod +
[docs] def reverse_transaction(self, transaction_id): + raise NotImplementedError - def get_transaction(self, *args, **kw):
-
[docs] raise NotImplementedError() - - def get_account(self, *args, **kw):
-
[docs] raise NotImplementedError() - - def get_accounts(self, *args, **kw):
-
[docs] raise NotImplementedError() -
+
+
[docs]class TransactionNotFound(AccountingException): + pass
+
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/storage/ledgercli.html b/doc/build/html/_modules/accounting/storage/ledgercli.html index b4a0d3b..01e9fb3 100644 --- a/doc/build/html/_modules/accounting/storage/ledgercli.html +++ b/doc/build/html/_modules/accounting/storage/ledgercli.html @@ -8,7 +8,7 @@ accounting.storage.ledgercli — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -52,17 +58,23 @@

Source code for accounting.storage.ledgercli

+# Part of accounting-api project:
+# https://gitorious.org/conservancy/accounting-api
+# License: AGPLv3-or-later
+
 import sys
 import subprocess
 import logging
 import time
+import re
 
 from datetime import datetime
 from xml.etree import ElementTree
 from contextlib import contextmanager
 
+from accounting.exceptions import AccountingException
 from accounting.models import Account, Transaction, Posting, Amount
-from accounting.storage import Storage
+from accounting.storage import Storage, TransactionNotFound
 
 _log = logging.getLogger(__name__)
 
@@ -215,11 +227,14 @@
         :class:`~accounting.models.Transaction` instance in
         :data:`transaction`.
         '''
-        if not transaction.metadata.get('Id'):
+        if transaction.id is None:
+            _log.debug('No ID found. Generating an ID.')
             transaction.generate_id()
 
+        transaction.metadata.update({'Id': transaction.id})
+
         transaction_template = ('\n{date} {t.payee}\n'
-                                '{tags}'
+                                '{metadata}'
                                 '{postings}')
 
         metadata_template = '   ;{0}: {1}\n'
@@ -235,7 +250,7 @@
         output += transaction_template.format(
             date=transaction.date.strftime('%Y-%m-%d'),
             t=transaction,
-            tags=''.join([
+            metadata=''.join([
                 metadata_template.format(k, v)
                 for k, v in transaction.metadata.items()]),
             postings=''.join([posting_template.format(
@@ -251,6 +266,8 @@
             f.write(output)
 
         _log.debug('written to file: %s', output)
+
+        return transaction.id
 
[docs] def bal(self): output = self.send_command('xml') @@ -299,6 +316,16 @@
[docs] def get_transactions(self): return self.reg()
+
[docs] def get_transaction(self, transaction_id): + transactions = self.get_transactions() + + for transaction in transactions: + if transaction.id == transaction_id: + return transaction + + raise TransactionNotFound('No transaction with id %s found', + transaction_id) +
[docs] def reg(self): output = self.send_command('xml') @@ -354,15 +381,125 @@ metadata.update({key: value}) # Add a Transaction instance to the list - id = metadata.pop('Id') + try: + id = metadata.pop('Id') + except KeyError: + _log.warning('Transaction on %s with payee %s does not have an' + ' Id attribute. A temporary ID will be used.', + date, payee) + id = 'NO-ID' entries.append( Transaction(id=id, date=date, payee=payee, postings=postings, metadata=metadata)) return entries
+
[docs] def delete_transaction(self, transaction_id): + ''' + Delete a transaction from the ledger file. + + This method opens the ledger file, loads all lines into memory and + looks for the transaction_id, then looks for the bounds of that + transaction in the ledger file, removes all lines within the bounds of + the transaction and removes them, then writes the lines back to the + ledger file. + + Exceptions: + + - RuntimeError: If all boundaries to the transaction are not found + - TransactionNotFound: If no such transaction_id can be found in + :data:`self.ledger_file` + ''' + f = open(self.ledger_file, 'r') + + lines = [i for i in f] + + # A mapping of line meanings and their line numbers as found by the + # following logic + semantic_lines = dict( + id_location=None, + transaction_start=None, + next_transaction_or_eof=None + ) + + for i, line in enumerate(lines): + if transaction_id in line: + semantic_lines['id_location'] = i + break + + if not semantic_lines['id_location']: + raise TransactionNotFound( + 'No transaction with ID "{0}" found'.format(transaction_id)) + + transaction_start_pattern = re.compile(r'^\S') + + cursor = semantic_lines['id_location'] - 1 + + # Find the first line of the transaction + while True: + if transaction_start_pattern.match(lines[cursor]): + semantic_lines['transaction_start'] = cursor + break + + cursor -= 1 + + cursor = semantic_lines['id_location'] + 1 + + # Find the last line of the transaction + while True: + try: + if transaction_start_pattern.match(lines[cursor]): + semantic_lines['next_transaction_or_eof'] = cursor + break + except IndexError: + # Set next_line_without_starting_space_or_end_of_file to + # the cursor. The cursor will be an index not included in the + # list of lines + semantic_lines['next_transaction_or_eof'] = cursor + break + + cursor += 1 + + if not all(map(lambda v: v is not None, semantic_lines.values())): + raise RuntimeError('Could not find all the values necessary for' + ' safe deletion of a transaction.') + + del_start = semantic_lines['transaction_start'] + + if len(lines) == semantic_lines['next_transaction_or_eof']: + _log.debug('There are no transactions below the transaction being' + ' deleted. The line before the first line of the' + ' transaction will be deleted.') + # Delete the preceding line to make the file + del_start -= 1 + + del lines[del_start:semantic_lines['next_transaction_or_eof']] + + with open(self.ledger_file, 'w') as f: + for line in lines: + f.write(line) +
[docs] def update_transaction(self, transaction): - _log.debug('DUMMY: Updated transaction: %s', transaction) + ''' + Update a transaction in the ledger file. + + Takes a :class:`~accounting.models.Transaction` object and removes + the old transaction using :data:`transaction.id` from the passed + :class:`~accounting.models.Transaction` instance and adds + :data:`transaction` to the database. + ''' + if not transaction.id: + return AccountingException(('The transaction {0} has no' + ' id attribute').format(transaction)) + + old_transaction = self.get_transaction(transaction.id) + + self.delete_transaction(transaction.id) + + self.add_transaction(transaction) + + _log.debug('Updated transaction from: %s to: %s', old_transaction, + transaction)
[docs]def main(argv=None): @@ -392,6 +529,17 @@
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/storage/sql.html b/doc/build/html/_modules/accounting/storage/sql.html index e66adf4..21e8305 100644 --- a/doc/build/html/_modules/accounting/storage/sql.html +++ b/doc/build/html/_modules/accounting/storage/sql.html @@ -8,7 +8,7 @@ accounting.storage.sql — Accounting API 0.1-beta documentation - + - + + + + + + + + @@ -52,6 +58,10 @@

Source code for accounting.storage.sql

+# Part of accounting-api project:
+# https://gitorious.org/conservancy/accounting-api
+# License: AGPLv3-or-later
+
 import logging
 import json
 
@@ -62,18 +72,17 @@
 from accounting.models import Transaction, Posting, Amount
 
 _log = logging.getLogger(__name__)
-db = None
+db = SQLAlchemy()
 
 
 
[docs]class SQLStorage(Storage): def __init__(self, app=None): - global db if not app: raise Exception('Missing app keyword argument') self.app = app - db = self.db = SQLAlchemy(app) + db.init_app(app) from .models import Transaction as SQLTransaction, \ Posting as SQLPosting, Amount as SQLAmount @@ -123,7 +132,7 @@ _t.payee = transaction.payee _t.meta = json.dumps(transaction.metadata) - self.db.session.add(_t) + db.session.add(_t) for posting in transaction.postings: _p = self.Posting() @@ -133,9 +142,9 @@ _p.amount = self.Amount(symbol=posting.amount.symbol, amount=posting.amount.amount) - self.db.session.add(_p) + db.session.add(_p) - self.db.session.commit()
+ db.session.commit()
@@ -143,6 +152,17 @@
+

Related Topics

+
- - + + \ No newline at end of file diff --git a/doc/build/html/_modules/accounting/storage/sql/models.html b/doc/build/html/_modules/accounting/storage/sql/models.html index adb0be6..c0d6a08 100644 --- a/doc/build/html/_modules/accounting/storage/sql/models.html +++ b/doc/build/html/_modules/accounting/storage/sql/models.html @@ -8,7 +8,7 @@ accounting.storage.sql.models — Accounting API 0.1-beta documentation - + - + + + + + + + +