experimental-accounting-api/accounting/web.py

203 lines
5.4 KiB
Python
Raw Normal View History

# Part of accounting-api project:
# https://gitorious.org/conservancy/accounting-api
# License: AGPLv3-or-later
'''
This module contains the high-level webservice logic such as the Flask setup
and the Flask endpoints.
'''
import sys
import logging
import argparse
from flask import Flask, jsonify, request, render_template, abort
from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand
2013-12-20 13:51:32 +00:00
from accounting.models import Transaction
2013-12-17 10:18:35 +00:00
from accounting.storage import Storage
from accounting.storage.ledgercli import Ledger
from accounting.storage.sql import SQLStorage
from accounting.transport import AccountingEncoder, AccountingDecoder
from accounting.exceptions import AccountingException, TransactionNotFound
2013-12-20 13:51:32 +00:00
from accounting.decorators import jsonify_exceptions, cors
app = Flask('accounting')
app.config.from_pyfile('config.py')
app.ledger = Storage()
def init_ledger():
app.ledger = Ledger(app)
# These will convert output from our internal classes to JSON and back
app.json_encoder = AccountingEncoder
app.json_decoder = AccountingDecoder
@app.route('/')
def index():
''' Hello World! '''
return 'Hello World!'
2013-12-20 13:51:32 +00:00
@app.route('/client')
def client():
return render_template('client.html')
@app.route('/transaction', methods=['OPTIONS'])
@cors()
@jsonify_exceptions
def transaction_options():
return jsonify(status='OPTIONS')
@app.route('/transaction/<string:transaction_id>', methods=['OPTIONS'])
@cors()
@jsonify_exceptions
def transaction_by_id_options(transaction_id=None):
return jsonify(status='OPTIONS')
2013-12-12 09:27:51 +00:00
@app.route('/transaction', methods=['GET'])
2013-12-20 13:51:32 +00:00
@app.route('/transaction/<string:transaction_id>', methods=['GET'])
@cors()
@jsonify_exceptions
def transaction_get(transaction_id=None):
'''
Returns the JSON-serialized output of :meth:`accounting.Ledger.reg`
'''
2013-12-20 13:51:32 +00:00
if transaction_id is None:
return jsonify(transactions=app.ledger.get_transactions())
2013-12-20 13:51:32 +00:00
try:
return jsonify(transaction=app.ledger.get_transaction(transaction_id))
except TransactionNotFound:
abort(404)
@app.route('/transaction/<string:transaction_id>', methods=['POST'])
2013-12-20 13:51:32 +00:00
@cors()
@jsonify_exceptions
def transaction_update(transaction_id=None):
if transaction_id is None:
raise AccountingException('The transaction ID cannot be None.')
transaction = request.json['transaction']
if transaction.id is not None and not transaction.id == transaction_id:
raise AccountingException('The transaction data has an ID attribute'
' and it is not the same ID as in the path')
elif transaction.id is None:
transaction.id = transaction_id
app.ledger.update_transaction(transaction)
return jsonify(status='OK')
@app.route('/transaction/<string:transaction_id>', methods=['DELETE'])
2013-12-20 13:51:32 +00:00
@cors()
@jsonify_exceptions
def transaction_delete(transaction_id=None):
if transaction_id is None:
raise AccountingException('Transaction ID cannot be None')
app.ledger.delete_transaction(transaction_id)
return jsonify(status='OK')
2013-12-10 23:25:16 +00:00
@app.route('/transaction', methods=['POST'])
2013-12-20 13:51:32 +00:00
@cors()
2013-12-10 23:25:16 +00:00
@jsonify_exceptions
2013-12-12 09:27:51 +00:00
def transaction_post():
2013-12-10 23:25:16 +00:00
'''
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
'''
2013-12-20 13:51:32 +00:00
if not isinstance(request.json, Transaction):
transactions = request.json.get('transactions')
else:
transactions = [request.json]
2013-12-10 23:25:16 +00:00
if not transactions:
raise AccountingException('No transaction data provided')
transaction_ids = []
2013-12-10 23:25:16 +00:00
for transaction in transactions:
transaction_ids.append(app.ledger.add_transaction(transaction))
2013-12-10 23:25:16 +00:00
return jsonify(status='OK', transaction_ids=transaction_ids)
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'))
init_ledger()
2013-12-17 10:18:35 +00:00
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__':
main()