r22936 - gnucash/trunk/src/optional/python-bindings - REST API Example for Python Bindings
Geert Janssens
gjanssens at code.gnucash.org
Thu May 2 12:34:03 EDT 2013
Author: gjanssens
Date: 2013-05-02 12:34:03 -0400 (Thu, 02 May 2013)
New Revision: 22936
Trac: http://svn.gnucash.org/trac/changeset/22936
Added:
gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/
gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/README
gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_rest.py
gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_simple.py
Modified:
gnucash/trunk/src/optional/python-bindings/Makefile.am
gnucash/trunk/src/optional/python-bindings/gnucash_core.py
Log:
REST API Example for Python Bindings
Initial version of REST API allowing minimal information about accounts, invoices and customers to be accessed in JSON format. Includes modifications to gnucash_core.py to add additional functions.
Author: Tom Lofts <dev at loftx.co.uk>
Modified: gnucash/trunk/src/optional/python-bindings/Makefile.am
===================================================================
--- gnucash/trunk/src/optional/python-bindings/Makefile.am 2013-05-02 14:43:09 UTC (rev 22935)
+++ gnucash/trunk/src/optional/python-bindings/Makefile.am 2013-05-02 16:34:03 UTC (rev 22936)
@@ -92,7 +92,10 @@
example_scripts/change_tax_code.py \
example_scripts/account_analysis.py \
example_scripts/new_book_with_opening_balances.py \
- example_scripts/test_imbalance_transaction.py
+ example_scripts/test_imbalance_transaction.py \
+ example_scripts/rest-api/gnucash_rest.py \
+ example_scripts/rest-api/gnucash_simple.py \
+ example_scripts/rest-api/README
MAINTAINERCLEANFILES = gnucash_core.c
Added: gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/README
===================================================================
--- gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/README (rev 0)
+++ gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/README 2013-05-02 16:34:03 UTC (rev 22936)
@@ -0,0 +1,48 @@
+A Python based REST framework for the Gnucash accounting application
+
+**Please note this is a very early work in progress and should not be run against live or production data.**
+
+This project is designed to allow other applications to easily extract (and hopefully insert and update at some point) data from Gnucash via a RESTful API.
+
+The API is built on the existing Python bindings and uses Flask to serve responses in JSON.
+
+Currently only accounts, customers and invoices can be accessed via the framework
+
+Accounts: <http://localhost:5000/accounts>
+Individual accounr: <http://localhost:5000/accounts/XXXX>
+
+Invoices: <http://localhost:5000/invoices>
+Individual invoice: <http://localhost:5000/invoices/XXXX>
+
+Customers: <http://localhost:5000/customers>
+Individual customer: <http://localhost:5000/customers/XXXX>
+
+Invoices can be filtered via the following parameters:
+
+is_active 1 or 0
+is_paid 1 or 0
+
+e.g. <http://localhost:5000/invoices?is_active=1&is_paid=0>
+
+Usage
+-----
+
+**As this is work in progress you'll need a knowledge of building Gnucash from source and experience with python.**
+
+Full details to be provided at a later date, but in short you'll need a copy of Gnucash built with Python bindings and the Flask module installed.
+
+Rrun `python ./gnucash_rest <connection string>` to start the server on <http://localhost:5000> e.g `python ./gnucash_rest mysql://user:password@127.0.0.1/gnucash_test`
+
+Navigate to <http://localhost:5000/invoices> and you should get your invoices in JSON format.
+
+Files
+-----
+
+`gnucash_rest.py` - The Flask app which responds to REST requests with JSON responses
+
+`gnucash_simple.py` - A helper file to convert Gnucash objects into dictionaries for easier conversion to JSON.
+
+Future
+------
+
+I'm using the API already to integrate Gnucash invoices with other systems, so I'll be adding features as and when I need them. The format of the API is likely to change as development continues.
Added: gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_rest.py
===================================================================
--- gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_rest.py (rev 0)
+++ gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_rest.py 2013-05-02 16:34:03 UTC (rev 22936)
@@ -0,0 +1,220 @@
+#!/usr/bin/python
+
+import gnucash
+import gnucash_simple
+import json
+import atexit
+from flask import Flask, abort, request
+import sys
+import getopt
+
+from gnucash import \
+ QOF_QUERY_AND, \
+ QOF_QUERY_OR, \
+ QOF_QUERY_NAND, \
+ QOF_QUERY_NOR, \
+ QOF_QUERY_XOR
+
+from gnucash import \
+ QOF_STRING_MATCH_NORMAL, \
+ QOF_STRING_MATCH_CASEINSENSITIVE
+
+from gnucash import \
+ QOF_COMPARE_LT, \
+ QOF_COMPARE_LTE, \
+ QOF_COMPARE_EQUAL, \
+ QOF_COMPARE_GT, \
+ QOF_COMPARE_GTE, \
+ QOF_COMPARE_NEQ
+
+from gnucash import \
+ INVOICE_TYPE
+
+from gnucash import \
+ INVOICE_IS_PAID
+
+app = Flask(__name__)
+app.debug = True
+
+ at app.route('/accounts')
+def api_accounts():
+
+ accounts = getAccounts(session.book)
+
+ return accounts
+
+ at app.route('/accounts/<guid>')
+def api_account(guid):
+
+ account = getAccount(session.book, guid)
+
+ if account is None:
+ abort(404)
+ else:
+ return account
+
+ at app.route('/invoices')
+def api_invoices():
+
+ is_paid = request.args.get('is_paid', None)
+ is_active = request.args.get('is_active', None)
+
+ if is_paid == '1':
+ is_paid = 1
+ elif is_paid == '0':
+ is_paid = 0
+ else:
+ is_paid = None
+
+ if is_active == '1':
+ is_active = 1
+ elif is_active == '0':
+ is_active = 0
+ else:
+ is_active = None
+
+ invoices = getInvoices(session.book, is_paid, is_active)
+
+ return invoices
+
+ at app.route('/invoices/<id>')
+def api_invoice(id):
+
+ invoice = getInvoice(session.book, id)
+
+ if invoice is None:
+ abort(404)
+ else:
+ return invoice
+
+
+ at app.route('/customers')
+def api_customers():
+
+ customers = getCustomers(session.book)
+
+ return customers
+
+ at app.route('/customers/<id>')
+def api_customer(id):
+
+ customer = getCustomer(session.book, id)
+
+ if customer is None:
+ abort(404)
+ else:
+ return customer
+
+def getCustomers(book):
+
+ query = gnucash.Query()
+ query.search_for('gncCustomer')
+ query.set_book(book)
+ customers = []
+
+ for result in query.run():
+ customers.append(gnucash_simple.customerToDict(gnucash.gnucash_business.Customer(instance=result)))
+
+ query.destroy()
+
+ return json.dumps(customers)
+
+def getCustomer(book, id):
+
+ customer = book.CustomerLookupByID(id)
+
+ if customer is None:
+ return None
+ else:
+ return json.dumps(gnucash_simple.customerToDict(customer))
+
+def getAccounts(book):
+
+ accounts = gnucash_simple.accountToDict(book.get_root_account())
+
+ return json.dumps(accounts)
+
+def getAccount(book, guid):
+
+ account_guid = gnucash.gnucash_core.GUID()
+ gnucash.gnucash_core.GUIDString(guid, account_guid)
+
+ account = gnucash_simple.accountToDict(account_guid.AccountLookup(book))
+
+ if account is None:
+ return None
+ else:
+ return json.dumps(account)
+
+
+def getInvoices(book, is_paid, is_active):
+
+ query = gnucash.Query()
+ query.search_for('gncInvoice')
+ query.set_book(book)
+
+ if is_paid == 0:
+ query.add_boolean_match([INVOICE_IS_PAID], False, QOF_QUERY_AND)
+ elif is_paid == 1:
+ query.add_boolean_match([INVOICE_IS_PAID], True, QOF_QUERY_AND)
+
+ # active = JOB_IS_ACTIVE
+ if is_active == 0:
+ query.add_boolean_match(['active'], False, QOF_QUERY_AND)
+ elif is_active == 1:
+ query.add_boolean_match(['active'], True, QOF_QUERY_AND)
+
+ # return only invoices (1 = invoices)
+ pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 1)
+ query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
+
+ invoices = []
+
+ for result in query.run():
+ invoices.append(gnucash_simple.invoiceToDict(gnucash.gnucash_business.Invoice(instance=result)))
+
+ query.destroy()
+
+ return json.dumps(invoices)
+
+def getInvoice(book, id):
+
+ invoice = book.InvoiceLookupByID(id)
+
+ if invoice is None:
+ return None
+ else:
+ #print invoiceToDict(invoice)
+ return json.dumps(gnucash_simple.invoiceToDict(invoice))
+
+def shutdown():
+ session.end()
+ session.destroy()
+
+try:
+ options, arguments = getopt.getopt(sys.argv[1:], 'h:', ['host='])
+except getopt.GetoptError as err:
+ print str(err) # will print something like "option -a not recognized"
+ print 'Usage: python-rest.py <connection string>'
+ sys.exit(2)
+
+if len(arguments) != 1:
+ print 'Usage: python-rest.py <connection string>'
+ sys.exit(2)
+
+#set default host for flash
+host = '127.0.0.1'
+
+#allow host option to be changed
+for option, value in options:
+ if option in ("-h", "--host"):
+ host = value
+
+#start gnucash session base on connection string argument
+session = gnucash.Session(arguments[0], ignore_lock=True)
+
+# register method to close gnucash connection gracefully
+atexit.register(shutdown)
+
+# start Flask server
+app.run(host=host)
Property changes on: gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_rest.py
___________________________________________________________________
Added: svn:executable
+ *
Added: gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_simple.py
===================================================================
--- gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_simple.py (rev 0)
+++ gnucash/trunk/src/optional/python-bindings/example_scripts/rest-api/gnucash_simple.py 2013-05-02 16:34:03 UTC (rev 22936)
@@ -0,0 +1,152 @@
+import gnucash
+from gnucash.gnucash_business import Entry
+
+def addressToDict(address):
+ if address is None:
+ return None
+ else:
+ simple_address = {}
+ simple_address['name'] = address.GetName();
+ simple_address['line_1'] = address.GetAddr1();
+ simple_address['line_2'] = address.GetAddr2();
+ simple_address['line_3'] = address.GetAddr3();
+ simple_address['line_4'] = address.GetAddr4();
+ simple_address['phone'] = address.GetPhone();
+ simple_address['fax'] = address.GetFax();
+ simple_address['email'] = address.GetEmail();
+
+ return simple_address
+
+def vendorToDict(vendor):
+
+ if vendor is None:
+ return None
+ else:
+ simple_vendor = {}
+ simple_vendor['name'] = vendor.GetName()
+ simple_vendor['id'] = vendor.GetID()
+ simple_vendor['notes'] = vendor.GetNotes()
+ simple_vendor['active'] = vendor.GetActive()
+ simple_vendor['currency'] = vendor.GetCurrency().get_mnemonic()
+ simple_vendor['tax_table_override'] = vendor.GetTaxTableOverride()
+ simple_vendor['address'] = addressToDict(vendor.GetAddr())
+ simple_vendor['tax_included'] = vendor.GetTaxIncluded()
+
+ return simple_vendor
+
+def customerToDict(customer):
+
+ if customer is None:
+ return None
+ else:
+ simple_customer = {}
+ simple_customer['name'] = customer.GetName()
+ simple_customer['id'] = customer.GetID()
+ simple_customer['notes'] = customer.GetNotes()
+ simple_customer['active'] = customer.GetActive()
+ simple_customer['discount'] = customer.GetDiscount().to_double()
+ simple_customer['credit'] = customer.GetCredit().to_double()
+ simple_customer['currency'] = customer.GetCurrency().get_mnemonic()
+ simple_customer['tax_table_override'] = customer.GetTaxTableOverride()
+ simple_customer['address'] = addressToDict(customer.GetAddr())
+ simple_customer['shipping_address'] = addressToDict(customer.GetShipAddr())
+ simple_customer['tax_included'] = customer.GetTaxIncluded()
+
+ return simple_customer
+
+def transactionToDict(transaction):
+ if transaction is None:
+ return None
+ else:
+ simple_transaction = {}
+ simple_transaction['num'] = transaction.GetNum()
+ simple_transaction['notes'] = transaction.GetNotes()
+ simple_transaction['is_closing_txn'] = transaction.GetIsClosingTxn()
+ simple_transaction['count_splits'] = transaction.CountSplits()
+ simple_transaction['has_reconciled_splits'] = transaction.HasReconciledSplits()
+ simple_transaction['currency'] = transaction.GetCurrency().get_mnemonic()
+ simple_transaction['imbalance_value'] = transaction.GetImbalanceValue().to_double()
+ simple_transaction['is_balanced'] = transaction.IsBalanced()
+ simple_transaction['date'] = transaction.GetDate()
+ simple_transaction['date_posted'] = transaction.RetDatePostedTS().strftime('%Y-%m-%d')
+ simple_transaction['date_entered'] = transaction.RetDateEnteredTS().strftime('%Y-%m-%d')
+ simple_transaction['date_due'] = transaction.RetDateDueTS().strftime('%Y-%m-%d')
+ simple_transaction['void_status'] = transaction.GetVoidStatus()
+ simple_transaction['void_time'] = transaction.GetVoidTime().strftime('%Y-%m-%d')
+
+ return simple_transaction
+
+def invoiceToDict(invoice):
+
+ if invoice is None:
+ return None
+ else:
+ simple_invoice = {}
+ simple_invoice['id'] = invoice.GetID()
+ simple_invoice['type'] = invoice.GetType()
+ simple_invoice['date_opened'] = invoice.GetDateOpened().strftime('%Y-%m-%d')
+ simple_invoice['date_posted'] = invoice.GetDatePosted().strftime('%Y-%m-%d')
+ simple_invoice['date_due'] = invoice.GetDateDue().strftime('%Y-%m-%d')
+ simple_invoice['notes'] = invoice.GetNotes()
+ simple_invoice['active'] = invoice.GetActive()
+ simple_invoice['currency'] = invoice.GetCurrency().get_mnemonic()
+ simple_invoice['owner'] = vendorToDict(invoice.GetOwner())
+ simple_invoice['owner_type'] = invoice.GetOwnerType()
+ simple_invoice['billing_id'] = invoice.GetBillingID()
+ simple_invoice['to_charge_amount'] = invoice.GetToChargeAmount().to_double()
+ simple_invoice['total'] = invoice.GetTotal().to_double()
+ simple_invoice['total_subtotal'] = invoice.GetTotalSubtotal().to_double()
+ simple_invoice['total_tax'] = invoice.GetTotalTax().to_double()
+ simple_invoice['entries'] = {}
+ for n, entry in enumerate(invoice.GetEntries()):
+ if type(entry) != Entry:
+ entry=Entry(instance=entry)
+ simple_invoice['entries'][n] = entryToDict(entry)
+ simple_invoice['posted'] = invoice.IsPosted()
+ simple_invoice['paid'] = invoice.IsPaid()
+
+ return simple_invoice
+
+def entryToDict(entry):
+
+ if entry is None:
+ return None
+ else:
+ simple_entry = {}
+ simple_entry['date'] = entry.GetDate().strftime('%Y-%m-%d')
+ simple_entry['date_entered'] = entry.GetDateEntered().strftime('%Y-%m-%d')
+ simple_entry['description'] = entry.GetDescription()
+ simple_entry['action'] = entry.GetAction()
+ simple_entry['notes'] = entry.GetNotes()
+ simple_entry['quantity'] = gnucash.GncNumeric(instance=entry.GetQuantity()).to_double()
+ simple_entry['inv_price'] = gnucash.GncNumeric(instance=entry.GetInvPrice()).to_double()
+ simple_entry['discount'] = gnucash.GncNumeric(instance=entry.GetInvDiscount()).to_double()
+ simple_entry['discounted_type'] = entry.GetInvDiscountType()
+ simple_entry['discounted_how'] = entry.GetInvDiscountHow()
+ simple_entry['inv_taxable'] = entry.GetInvTaxable()
+ simple_entry['inv_tax_included'] = entry.GetInvTaxIncluded()
+ simple_entry['inv_tax_table_override'] = entry.GetInvTaxTable()
+ simple_entry['bill_price'] = gnucash.GncNumeric(instance=entry.GetBillPrice()).to_double()
+ simple_entry['bill_taxable'] = entry.GetBillTaxable()
+ simple_entry['bill_tax_included'] = entry.GetBillTaxIncluded()
+ simple_entry['bill_tax_table'] = entry.GetBillTaxTable()
+ simple_entry['billable'] = entry.GetBillable()
+ simple_entry['bill_payment'] = entry.GetBillPayment()
+ simple_entry['is_open'] = entry.IsOpen()
+
+ return simple_entry
+
+
+def accountToDict(account):
+
+ if account is None:
+ return None
+ else:
+ simple_account = {}
+ simple_account['name'] = account.GetName()
+ simple_account['guid'] = account.GetGUID().to_string()
+ simple_account['subaccounts'] = []
+ for n, subaccount in enumerate(account.get_children_sorted()):
+ simple_account['subaccounts'].append(accountToDict(subaccount))
+
+ return simple_account
\ No newline at end of file
Modified: gnucash/trunk/src/optional/python-bindings/gnucash_core.py
===================================================================
--- gnucash/trunk/src/optional/python-bindings/gnucash_core.py 2013-05-02 14:43:09 UTC (rev 22935)
+++ gnucash/trunk/src/optional/python-bindings/gnucash_core.py 2013-05-02 16:34:03 UTC (rev 22936)
@@ -666,6 +666,10 @@
GUID.add_method('xaccTransLookup', 'TransLookup')
GUID.add_method('xaccSplitLookup', 'SplitLookup')
+## define addition methods for GUID object - do we need these
+GUID.add_method('guid_to_string', 'to_string')
+#GUID.add_method('string_to_guid', 'string_to_guid')
+
guid_dict = {
'copy' : GUID,
'TransLookup': Transaction,
@@ -674,6 +678,12 @@
}
methods_return_instance(GUID, guid_dict)
+#GUIDString
+class GUIDString(GnuCashCoreClass):
+ pass
+
+GUIDString.add_constructor_and_methods_with_prefix('string_', 'to_guid')
+
#Query
from gnucash_core_c import \
QOF_QUERY_AND, \
@@ -682,7 +692,47 @@
QOF_QUERY_NOR, \
QOF_QUERY_XOR
+from gnucash_core_c import \
+ QOF_STRING_MATCH_NORMAL, \
+ QOF_STRING_MATCH_CASEINSENSITIVE
+
+from gnucash_core_c import \
+ QOF_COMPARE_LT, \
+ QOF_COMPARE_LTE, \
+ QOF_COMPARE_EQUAL, \
+ QOF_COMPARE_GT, \
+ QOF_COMPARE_GTE, \
+ QOF_COMPARE_NEQ
+
+from gnucash_core_c import \
+ INVOICE_TYPE
+
+from gnucash_core_c import \
+ INVOICE_IS_PAID
+
class Query(GnuCashCoreClass):
pass
Query.add_constructor_and_methods_with_prefix('qof_query_', 'create')
+
+Query.add_method('qof_query_set_book', 'set_book')
+Query.add_method('qof_query_search_for', 'search_for')
+Query.add_method('qof_query_run', 'run')
+Query.add_method('qof_query_add_term', 'add_term')
+Query.add_method('qof_query_add_boolean_match', 'add_boolean_match')
+Query.add_method('qof_query_destroy', 'destroy')
+
+class QueryStringPredicate(GnuCashCoreClass):
+ pass
+
+QueryStringPredicate.add_constructor_and_methods_with_prefix('qof_query_', 'string_predicate')
+
+class QueryBooleanPredicate(GnuCashCoreClass):
+ pass
+
+QueryBooleanPredicate.add_constructor_and_methods_with_prefix('qof_query_', 'boolean_predicate')
+
+class QueryInt32Predicate(GnuCashCoreClass):
+ pass
+
+QueryInt32Predicate.add_constructor_and_methods_with_prefix('qof_query_', 'int32_predicate')
\ No newline at end of file
More information about the gnucash-changes
mailing list