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