mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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@loftx.co.uk> git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@22936 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
@@ -92,7 +92,10 @@ EXTRA_DIST = \
|
||||
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
|
||||
|
||||
|
||||
48
src/optional/python-bindings/example_scripts/rest-api/README
Normal file
48
src/optional/python-bindings/example_scripts/rest-api/README
Normal file
@@ -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.
|
||||
220
src/optional/python-bindings/example_scripts/rest-api/gnucash_rest.py
Executable file
220
src/optional/python-bindings/example_scripts/rest-api/gnucash_rest.py
Executable file
@@ -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
|
||||
|
||||
@app.route('/accounts')
|
||||
def api_accounts():
|
||||
|
||||
accounts = getAccounts(session.book)
|
||||
|
||||
return accounts
|
||||
|
||||
@app.route('/accounts/<guid>')
|
||||
def api_account(guid):
|
||||
|
||||
account = getAccount(session.book, guid)
|
||||
|
||||
if account is None:
|
||||
abort(404)
|
||||
else:
|
||||
return account
|
||||
|
||||
@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
|
||||
|
||||
@app.route('/invoices/<id>')
|
||||
def api_invoice(id):
|
||||
|
||||
invoice = getInvoice(session.book, id)
|
||||
|
||||
if invoice is None:
|
||||
abort(404)
|
||||
else:
|
||||
return invoice
|
||||
|
||||
|
||||
@app.route('/customers')
|
||||
def api_customers():
|
||||
|
||||
customers = getCustomers(session.book)
|
||||
|
||||
return customers
|
||||
|
||||
@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)
|
||||
@@ -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
|
||||
@@ -666,6 +666,10 @@ GUID.add_method('xaccAccountLookup', 'AccountLookup')
|
||||
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 @@ guid_dict = {
|
||||
}
|
||||
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 @@ from gnucash_core_c import \
|
||||
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')
|
||||
Reference in New Issue
Block a user