Two-way conversion from internal representation and JSON

This commit is contained in:
Joar Wandborg 2013-12-10 23:22:57 +01:00
parent 4b5eca291b
commit 63c7b70000
3 changed files with 147 additions and 41 deletions

View file

@ -43,12 +43,12 @@ class Ledger:
process = self.get_process()
self.locked = True
_log.debug('lock enabled')
_log.debug('Lock enabled')
yield process
self.locked = False
_log.debug('lock disabled')
_log.debug('Lock disabled')
def assemble_arguments(self):
return [
@ -58,7 +58,7 @@ class Ledger:
]
def init_process(self):
_log.debug('starting ledger process')
_log.debug('Starting ledger process...')
self.ledger_process = subprocess.Popen(
self.assemble_arguments(),
stdout=subprocess.PIPE,
@ -78,15 +78,12 @@ class Ledger:
output = b''
while True:
# _log.debug('reading data')
line = p.stdout.read(1) # XXX: This is a hack
# _log.debug('line: %s', line)
output += line
if b'\n] ' in output:
_log.debug('found prompt!')
_log.debug('Found prompt!')
break
output = output[:-3] # Cut away the prompt
@ -107,6 +104,11 @@ class Ledger:
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
def bal(self):
@ -131,8 +133,12 @@ class Ledger:
amounts = []
account_amounts = account.findall('./account-total/balance/amount') or \
account.findall('./account-amount/amount')
# 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:
@ -140,6 +146,8 @@ class Ledger:
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,
@ -225,9 +233,18 @@ class Account:
def main(argv=None):
import argparse
if argv is None:
argv = sys.argv
logging.basicConfig(level=logging.INFO)
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())

49
accounting/transport.py Normal file
View file

@ -0,0 +1,49 @@
from flask import json
from accounting import Amount, Transaction, Posting, Account
class AccountingEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Account):
return dict(
__type__=o.__class__.__name__,
name=o.name,
amounts=o.amounts,
accounts=o.accounts
)
elif isinstance(o, Transaction):
return dict(
__type__=o.__class__.__name__,
date=o.date.strftime('%Y-%m-%d'),
payee=o.payee,
postings=o.postings
)
elif isinstance(o, Posting):
return dict(
__type__=o.__class__.__name__,
account=o.account,
amount=o.amount,
)
elif isinstance(o, Amount):
return dict(
__type__=o.__class__.__name__,
amount=o.amount,
symbol=o.symbol
)
return json.JSONEncoder.default(self, o)
class AccountingDecoder(json.JSONDecoder):
def __init__(self):
json.JSONDecoder.__init__(self, object_hook=self.dict_to_object)
def dict_to_object(self, d):
if '__type__' not in d:
return d
types = {c.__name__ : c for c in [Amount, Transaction, Posting,
Account]}
_type = d.pop('__type__')
return types[_type](**d)

View file

@ -1,46 +1,22 @@
import sys
import logging
import argparse
from flask import Flask, g, jsonify, json
from flask import Flask, g, jsonify, json, request
from accounting import Ledger, Account, Posting, Transaction, Amount
from accounting.transport import AccountingEncoder, AccountingDecoder
logging.basicConfig(level=logging.DEBUG)
app = Flask('accounting')
app.config.from_pyfile('config.py')
ledger = Ledger(ledger_file=app.config['LEDGER_FILE'])
class AccountingEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Account):
return dict(
name=o.name,
amounts=o.amounts,
accounts=o.accounts
)
elif isinstance(o, Transaction):
return dict(
date=o.date.strftime('%Y-%m-%d'),
payee=o.payee,
postings=o.postings
)
elif isinstance(o, Posting):
return dict(
account=o.account,
amount=o.amount,
)
elif isinstance(o, Amount):
return dict(
amount=o.amount,
symbol=o.symbol
)
return json.JSONEncoder.default(self, o)
# These will convert output from our internal classes to JSON and back
app.json_encoder = AccountingEncoder
app.json_decoder = AccountingDecoder
@app.route('/')
@ -50,19 +26,83 @@ def index():
@app.route('/balance')
def balance_report():
''' Returns the balance report from ledger '''
report_data = ledger.bal()
return jsonify(balance_report=report_data)
@app.route('/parse-json', methods=['POST'])
def parse_json():
r'''
Parses a __type__-annotated JSON payload and debug-logs the decoded version
of it.
Example:
wget http://127.0.0.1:5000/balance -O balance.json
curl -X POST -H 'Content-Type: application/json' -d @balance.json \
http://127.0.0.1/parse-json
# Logging output (linebreaks added for clarity)
DEBUG:accounting:json data: {'balance_report':
[<Account "None" [
<Amount $ 0>, <Amount KARMA 0>]
[<Account "Assets" [
<Amount $ 50>, <Amount KARMA 10>]
[<Account "Assets:Checking" [
<Amount $ 50>] []>,
<Account "Assets:Karma Account" [
<Amount KARMA 10>] []>]>,
<Account "Expenses" [
<Amount $ 500>]
[<Account "Expenses:Blah" [
<Amount $ 250>]
[<Account "Expenses:Blah:Hosting" [
<Amount $ 250>] []>]>,
<Account "Expenses:Foo" [
<Amount $ 250>] [
<Account "Expenses:Foo:Hosting" [
<Amount $ 250>] []>]>]>,
<Account "Income" [
<Amount $ -550>,
<Amount KARMA -10>]
[<Account "Income:Donation" [
<Amount $ -50>] []>,
<Account "Income:Foo" [
<Amount $ -500>]
[<Account "Income:Foo:Donation" [
<Amount $ -500>] []>]>,
<Account "Income:Karma" [
<Amount KARMA -10>] []>]>]>]}
'''
app.logger.debug('json data: %s', request.json)
return jsonify(foo='bar')
@app.route('/register')
def register_report():
''' Returns the register report from ledger '''
report_data = ledger.reg()
return jsonify(register_report=report_data)
def main():
def main(argv=None):
prog = __name__
if argv is None:
prog = sys.argv[0]
argv = sys.argv[1:]
parser = argparse.ArgumentParser(prog=prog)
parser.add_argument('-v', '--verbosity',
default='INFO',
help=('Filter logging output. Possible values:' +
' CRITICAL, ERROR, WARNING, INFO, DEBUG'))
args = parser.parse_args(argv)
logging.basicConfig(level=getattr(logging, args.verbosity, 'INFO'))
app.run(host=app.config['HOST'], port=app.config['PORT'])
if __name__ == '__main__':