Added /transaction endpoint

This commit is contained in:
Joar Wandborg 2013-12-11 00:25:16 +01:00
parent 63c7b70000
commit 6f2c875c7b
5 changed files with 125 additions and 2 deletions

View file

@ -111,6 +111,30 @@ class Ledger:
return output return output
def add_transaction(self, transaction):
transaction_template = ('\n{date} {t.payee}\n'
'{postings}')
posting_template = (' {account} {p.amount.symbol}'
' {p.amount.amount}\n')
output = b''
output += transaction_template.format(
date=transaction.date.strftime('%Y-%m-%d'),
t=transaction,
postings=''.join([posting_template.format(
p=p,
account=p.account + ' ' * (
80 - (len(p.account) + len(p.amount.symbol) +
len(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)
def bal(self): def bal(self):
output = self.send_command('xml') output = self.send_command('xml')

20
accounting/decorators.py Normal file
View file

@ -0,0 +1,20 @@
from functools import wraps
from flask import jsonify
from accounting.exceptions import AccountingException
def jsonify_exceptions(func):
'''
Wraps a Flask endpoint and catches any AccountingException-based
exceptions which are returned to the client as JSON.
'''
@wraps(func)
def wrapper(*args, **kw):
try:
return func(*args, **kw)
except AccountingException as exc:
return jsonify(error=exc)
return wrapper

6
accounting/exceptions.py Normal file
View file

@ -0,0 +1,6 @@
class AccountingException(Exception):
'''
Used as a base for exceptions that are returned to the caller via the
jsonify_exceptions decorator
'''
pass

View file

@ -1,3 +1,5 @@
from datetime import datetime
from flask import json from flask import json
from accounting import Amount, Transaction, Posting, Account from accounting import Amount, Transaction, Posting, Account
@ -30,6 +32,11 @@ class AccountingEncoder(json.JSONEncoder):
amount=o.amount, amount=o.amount,
symbol=o.symbol symbol=o.symbol
) )
elif isinstance(o, Exception):
return dict(
__type__=o.__class__.__name__,
args=o.args
)
return json.JSONEncoder.default(self, o) return json.JSONEncoder.default(self, o)
@ -46,4 +53,7 @@ class AccountingDecoder(json.JSONDecoder):
_type = d.pop('__type__') _type = d.pop('__type__')
if _type == 'Transaction':
d['date'] = datetime.strptime(d['date'], '%Y-%m-%d')
return types[_type](**d) return types[_type](**d)

View file

@ -2,10 +2,12 @@ import sys
import logging import logging
import argparse import argparse
from flask import Flask, g, jsonify, json, request from flask import Flask, jsonify, request
from accounting import Ledger, Account, Posting, Transaction, Amount from accounting import Ledger
from accounting.transport import AccountingEncoder, AccountingDecoder from accounting.transport import AccountingEncoder, AccountingDecoder
from accounting.exceptions import AccountingException
from accounting.decorators import jsonify_exceptions
app = Flask('accounting') app = Flask('accounting')
@ -32,6 +34,67 @@ def balance_report():
return jsonify(balance_report=report_data) return jsonify(balance_report=report_data)
@app.route('/transaction', methods=['POST'])
@jsonify_exceptions
def transaction():
'''
REST/JSON endpoint for transactions.
Current state:
Takes a POST request with a ``transactions`` JSON payload and writes it to
the ledger file.
Requires the ``transactions`` payload to be __type__-annotated:
.. code-block:: json
{
"transactions": [
{
"__type__": "Transaction",
"date": "2013-01-01",
"payee": "Kindly T. Donor",
"postings": [
{
"__type__": "Posting",
"account": "Income:Foo:Donation",
"amount": {
"__type__": "Amount",
"amount": "-100",
"symbol": "$"
}
},
{
"__type__": "Posting",
"account": "Assets:Checking",
"amount": {
"__type__": "Amount",
"amount": "100",
"symbol": "$"
}
}
]
}
}
becomes::
2013-01-01 Kindly T. Donor
Income:Foo:Donation $ -100
Assets:Checking $ 100
'''
transactions = request.json.get('transactions')
if not transactions:
raise AccountingException('No transaction data provided')
for transaction in transactions:
ledger.add_transaction(transaction)
return jsonify(foo='bar')
@app.route('/parse-json', methods=['POST']) @app.route('/parse-json', methods=['POST'])
def parse_json(): def parse_json():
r''' r'''