Bug 791831 - Add python3 support

This switches swig to use python 3 specific features when generating the
bindings, switches the build over to python 3 and makes the neccesary
python 2 to 3 conversions in the bindings and tests.
This commit is contained in:
Julian Wollrath 2018-03-15 09:35:31 +01:00 committed by John Ralls
parent 85ec07ec30
commit a43b115a45
12 changed files with 103 additions and 114 deletions

View File

@ -459,7 +459,7 @@ ENDIF (WITH_SQL)
# ############################################################
IF (WITH_PYTHON)
FIND_PACKAGE(PythonInterp)
FIND_PACKAGE(PythonInterp 3)
IF (NOT PYTHONINTERP_FOUND)
MESSAGE(SEND_ERROR "Python support enabled, but Python interpreter not found.")
ENDIF()
@ -468,14 +468,14 @@ IF (WITH_PYTHON)
MESSAGE(SEND_ERROR "Found python version ${PYTHON_VERSION_STRING}, but it's too old. Need python >= 2.4.0")
ENDIF()
FIND_PACKAGE(PythonLibs)
FIND_PACKAGE(PythonLibs 3)
IF (NOT PYTHONLIBS_FOUND)
MESSAGE(SEND_ERROR "Python support enabled, but Python libraries not found.")
ENDIF()
# Determine where to install the python libraries.
EXECUTE_PROCESS(
COMMAND ${PYTHON_EXECUTABLE} -c "from distutils import sysconfig; print sysconfig.get_python_lib(prefix='${CMAKE_INSTALL_PREFIX}', plat_specific=True)"
COMMAND ${PYTHON_EXECUTABLE} -c "from distutils import sysconfig; print(sysconfig.get_python_lib(prefix='${CMAKE_INSTALL_PREFIX}', plat_specific=True))"
RESULT_VARIABLE PYTHON_SYSCONFIG_RESULT
OUTPUT_VARIABLE PYTHON_SYSCONFIG_OUTPUT
ERROR_VARIABLE PYTHON_SYSCONFIG_ERROR

View File

@ -3,7 +3,7 @@
# >>> from gnucash import thingy
# instead of
# >>> from gnucash.gnucash_core import thingy
from gnucash_core import *
from gnucash.gnucash_core import *
## @file
# @brief helper file for the importing of gnucash
# @author Mark Jenkins, ParIT Worker Co-operative <mark@parit.ca>

View File

@ -170,7 +170,7 @@ def method_function_returns_instance_list(method_function, cls):
return new_function
def methods_return_instance_lists(cls, function_dict):
for func_name, instance_name in function_dict.iteritems():
for func_name, instance_name in iter(function_dict.items()):
setattr(cls, func_name,
method_function_returns_instance_list(
getattr(cls, func_name), instance_name))
@ -212,7 +212,7 @@ def extract_attributes_with_prefix(obj, prefix):
the attribute name, the attribute value, and the text that appears
after the prefix in the name
"""
for attr_name, attr_value in obj.__dict__.iteritems():
for attr_name, attr_value in iter(obj.__dict__.items()):
if attr_name.startswith(prefix):
after_prefix = attr_name[ len(prefix): ]
yield attr_name, attr_value, after_prefix
@ -221,7 +221,7 @@ def methods_return_instance(cls, function_dict):
"""Iterates through a dictionary of function name strings and instance names
and sets the function to return the associated instance
"""
for func_name, instance_name in function_dict.iteritems():
for func_name, instance_name in iter(function_dict.items()):
setattr(cls, func_name,
method_function_returns_instance( getattr(cls, func_name), instance_name))

View File

@ -26,18 +26,18 @@
# @author Jeff Green, ParIT Worker Co-operative <jeff@parit.ca>
# @ingroup python_bindings
import gnucash_core_c
import gnucash.gnucash_core_c as gnucash_core_c
from function_class import \
from gnucash.function_class import \
ClassFromFunctions, extract_attributes_with_prefix, \
default_arguments_decorator, method_function_returns_instance, \
methods_return_instance, methods_return_instance_lists
from gnucash_core import \
from gnucash.gnucash_core import \
GnuCashCoreClass, GncNumeric, GncCommodity, Transaction, \
Split, Book, GncLot, Account, GUID
from gnucash_core_c import GNC_OWNER_CUSTOMER, GNC_OWNER_JOB, \
from gnucash.gnucash_core_c import GNC_OWNER_CUSTOMER, GNC_OWNER_JOB, \
GNC_OWNER_EMPLOYEE, GNC_OWNER_VENDOR, \
GNC_PAYMENT_CASH, GNC_PAYMENT_CARD, \
GNC_DISC_PRETAX, GNC_DISC_SAMETIME, GNC_DISC_POSTTAX, \

View File

@ -28,15 +28,15 @@
# @author Jeff Green, ParIT Worker Co-operative <jeff@parit.ca>
# @ingroup python_bindings
import gnucash_core_c
import gnucash.gnucash_core_c as gnucash_core_c
from function_class import \
from gnucash.function_class import \
ClassFromFunctions, extract_attributes_with_prefix, \
default_arguments_decorator, method_function_returns_instance, \
methods_return_instance, process_list_convert_to_instance, \
method_function_returns_instance_list, methods_return_instance_lists
from gnucash_core_c import gncInvoiceLookup, gncInvoiceGetInvoiceFromTxn, \
from gnucash.gnucash_core_c import gncInvoiceLookup, gncInvoiceGetInvoiceFromTxn, \
gncInvoiceGetInvoiceFromLot, gncEntryLookup, gncInvoiceLookup, \
gncCustomerLookup, gncVendorLookup, gncJobLookup, gncEmployeeLookup, \
gncTaxTableLookup, gncTaxTableLookupByName, gnc_search_invoice_on_id, \
@ -112,7 +112,7 @@ class Session(GnuCashCoreClass):
# More background: https://bugzilla.gnome.org/show_bug.cgi?id=726891
if book_uri[:3] != "xml" or not is_new:
self.load()
except GnuCashBackendException, backend_exception:
except GnuCashBackendException as backend_exception:
self.end()
self.destroy()
raise
@ -174,66 +174,66 @@ class Book(GnuCashCoreClass):
get_table -- Returns a commodity lookup table, of type GncCommodityTable
"""
def InvoiceLookup(self, guid):
from gnucash_business import Invoice
from gnucash.gnucash_business import Invoice
return self.do_lookup_create_oo_instance(
gncInvoiceLookup, Invoice, guid.get_instance() )
def EntryLookup(self, guid):
from gnucash_business import Entry
from gnucash.gnucash_business import Entry
return self.do_lookup_create_oo_instance(
gncEntryLookup, Entry, guid.get_instance() )
def CustomerLookup(self, guid):
from gnucash_business import Customer
from gnucash.gnucash_business import Customer
return self.do_lookup_create_oo_instance(
gncCustomerLookup, Customer, guid.get_instance())
def JobLookup(self, guid):
from gnucash_business import Job
from gnucash.gnucash_business import Job
return self.do_lookup_create_oo_instance(
gncJobLookup, Job, guid.get_instance() )
def VendorLookup(self, guid):
from gnucash_business import Vendor
from gnucash.gnucash_business import Vendor
return self.do_lookup_create_oo_instance(
gncVendorLookup, Vendor, guid.get_instance() )
def EmployeeLookup(self, guid):
from gnucash_business import Employee
from gnucash.gnucash_business import Employee
return self.do_lookup_create_oo_instance(
gncEmployeeLookup, Employee, guid.get_instance() )
def TaxTableLookup(self, guid):
from gnucash_business import TaxTable
from gnucash.gnucash_business import TaxTable
return self.do_lookup_create_oo_instance(
gncTaxTableLookup, TaxTable, guid.get_instance() )
def TaxTableLookupByName(self, name):
from gnucash_business import TaxTable
from gnucash.gnucash_business import TaxTable
return self.do_lookup_create_oo_instance(
gncTaxTableLookupByName, TaxTable, name)
def TaxTableGetTables(self):
from gnucash_business import TaxTable
from gnucash.gnucash_business import TaxTable
return [ TaxTable(instance=item) for item in gncTaxTableGetTables(self.instance) ]
def BillLookupByID(self, id):
from gnucash_business import Bill
from gnucash.gnucash_business import Bill
return self.do_lookup_create_oo_instance(
gnc_search_bill_on_id, Bill, id)
def InvoiceLookupByID(self, id):
from gnucash_business import Invoice
from gnucash.gnucash_business import Invoice
return self.do_lookup_create_oo_instance(
gnc_search_invoice_on_id, Invoice, id)
def CustomerLookupByID(self, id):
from gnucash_business import Customer
from gnucash.gnucash_business import Customer
return self.do_lookup_create_oo_instance(
gnc_search_customer_on_id, Customer, id)
def VendorLookupByID(self, id):
from gnucash_business import Vendor
from gnucash.gnucash_business import Vendor
return self.do_lookup_create_oo_instance(
gnc_search_vendor_on_id, Vendor, id)
@ -294,11 +294,11 @@ class GncNumeric(GnuCashCoreClass):
return gnc_numeric_zero()
elif len(args) == 1:
arg = args[0]
if type(arg) in (int, long):
if isinstance(arg, int):
return gnc_numeric_create(arg ,1)
elif type(arg) == float:
elif isinstance(arg, float):
return double_to_gnc_numeric(arg, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER)
elif type(arg) == str:
elif isinstance(arg, str):
instance = gnc_numeric_zero()
if not string_to_gnc_numeric(arg, instance):
raise TypeError('Failed to convert to GncNumeric: ' + str(args))
@ -306,17 +306,17 @@ class GncNumeric(GnuCashCoreClass):
else:
raise TypeError('Only single int/float/str allowed: ' + str(args))
elif len(args) == 2:
if type(args[0]) == int and type(args[1]) == int:
if isinstance(args[0], int) and isinstance(args[1], int):
return gnc_numeric_create(*args)
else:
raise TypeError('Only two ints allowed: ' + str(args))
elif len(args) == 3:
if type(args[0]) == float \
and type(args[1]) in (int, long) \
if isinstance(args[0], float) \
and isinstance(args[1], int) \
and type(args[2]) == type(GNC_HOW_DENOM_FIXED):
return double_to_gnc_numeric(*args)
else:
raise TypeError('Only (float, int/long, GNC_HOW_RND_*) allowed: ' + str(args))
raise TypeError('Only (float, int, GNC_HOW_RND_*) allowed: ' + str(args))
else:
raise TypeError('Required single int/float/str or two ints: ' + str(args))
@ -324,14 +324,10 @@ class GncNumeric(GnuCashCoreClass):
from fractions import Fraction
return Fraction(self.num(), self.denom())
def __unicode__(self):
def __str__(self):
"""Returns a human readable numeric value string as UTF8."""
return gnc_numeric_to_string(self.instance)
def __str__(self):
"""returns a human readable numeric value string as bytes."""
return unicode(self).encode('utf-8')
class GncPrice(GnuCashCoreClass):
'''
Each priceEach price in the database represents an "instantaneous"
@ -411,7 +407,7 @@ class GncCommodityNamespace(GnuCashCoreClass):
class GncLot(GnuCashCoreClass):
def GetInvoiceFromLot(self):
from gnucash_business import Invoice
from gnucash.gnucash_business import Invoice
return self.do_lookup_create_oo_instance(
gncInvoiceGetInvoiceFromLot, Invoice )
@ -434,7 +430,7 @@ class Transaction(GnuCashCoreClass):
return self.GetSplitList().pop(n)
def GetInvoiceFromTxn(self):
from gnucash_business import Transaction
from gnucash.gnucash_business import Transaction
return self.do_lookup_create_oo_instance(
gncInvoiceGetInvoiceFromTxn, Transaction )
@ -738,18 +734,18 @@ class GUIDString(GnuCashCoreClass):
GUIDString.add_constructor_and_methods_with_prefix('string_', 'to_guid')
#Query
from gnucash_core_c import \
from gnucash.gnucash_core_c import \
QOF_QUERY_AND, \
QOF_QUERY_OR, \
QOF_QUERY_NAND, \
QOF_QUERY_NOR, \
QOF_QUERY_XOR
from gnucash_core_c import \
from gnucash.gnucash_core_c import \
QOF_STRING_MATCH_NORMAL, \
QOF_STRING_MATCH_CASEINSENSITIVE
from gnucash_core_c import \
from gnucash.gnucash_core_c import \
QOF_COMPARE_LT, \
QOF_COMPARE_LTE, \
QOF_COMPARE_EQUAL, \
@ -757,10 +753,10 @@ from gnucash_core_c import \
QOF_COMPARE_GTE, \
QOF_COMPARE_NEQ
from gnucash_core_c import \
from gnucash.gnucash_core_c import \
INVOICE_TYPE
from gnucash_core_c import \
from gnucash.gnucash_core_c import \
INVOICE_IS_PAID
class Query(GnuCashCoreClass):

View File

@ -5,8 +5,6 @@ import os
os.environ["GNC_UNINSTALLED"] = "1"
from test import test_support
from test_book import TestBook
from test_account import TestAccount
from test_split import TestSplit
@ -15,8 +13,5 @@ from test_business import TestBusiness
from test_commodity import TestCommodity, TestCommodityNamespace
from test_numeric import TestGncNumeric
def test_main():
test_support.run_unittest(TestBook, TestAccount, TestSplit, TestTransaction, TestBusiness, TestCommodity, TestCommodityNamespace, TestGncNumeric)
if __name__ == '__main__':
test_main()
unittest.main()

View File

@ -13,9 +13,9 @@ class AccountSession( BookSession ):
class TestAccount( AccountSession ):
def test_name(self):
NAME = "Money"
self.assertEquals( '', self.account.GetName() )
self.assertEqual( '', self.account.GetName() )
self.account.SetName(NAME)
self.assertEquals( NAME, self.account.GetName() )
self.assertEqual( NAME, self.account.GetName() )
def test_split(self):
SPLIT = Split(self.book)

View File

@ -22,12 +22,6 @@ class TestGncNumeric( TestCase ):
self.assertEqual(num.num(), 3)
self.assertEqual(num.denom(), 1)
def test_from_long(self):
num = GncNumeric(3L)
self.assertEqual(str(num), "3/1")
self.assertEqual(num.num(), 3)
self.assertEqual(num.denom(), 1)
with self.assertRaises(Exception) as context:
GncNumeric((2**64)+1)

View File

@ -17,9 +17,9 @@ class SplitSession( BookSession ):
class TestSplit( SplitSession ):
def test_memo(self):
MEMO = "cookie monster"
self.assertEquals( '', self.split.GetMemo() )
self.assertEqual( '', self.split.GetMemo() )
self.split.SetMemo(MEMO)
self.assertEquals( MEMO, self.split.GetMemo() )
self.assertEqual( MEMO, self.split.GetMemo() )
def test_account(self):
ACCT = Account(self.book)
@ -49,7 +49,7 @@ class TestSplit( SplitSession ):
self.split.SetParent(TRANS)
TRANS.SetCurrency(self.currency)
TRANS.SetDescription("Foo")
self.assertEquals( TRANS.GetDescription(), self.split.GetParent().GetDescription() )
self.assertEqual( TRANS.GetDescription(), self.split.GetParent().GetDescription() )
g_log_remove_handler(domain2, hdlr2)
g_log_remove_handler(domain1, hdlr1)

View File

@ -73,13 +73,13 @@ class TestTransaction( TransactionSession ):
self.assertFalse( self.trans.IsOpen() )
def test_rollback(self):
self.assertEquals( '', self.trans.GetDescription() )
self.assertEqual( '', self.trans.GetDescription() )
self.trans.BeginEdit()
DESC = 'Food'
self.trans.SetDescription(DESC)
self.assertEquals( DESC, self.trans.GetDescription() )
self.assertEqual( DESC, self.trans.GetDescription() )
self.trans.RollbackEdit()
self.assertEquals( '', self.trans.GetDescription() )
self.assertEqual( '', self.trans.GetDescription() )
def test_findsplit(self):
ACCT = Account(self.book)
@ -93,45 +93,45 @@ class TestTransaction( TransactionSession ):
self.assertTrue( SPLIT.Equal(self.split, True, False, False) )
def test_getsplitindex(self):
self.assertEquals( 0, self.trans.GetSplitIndex(self.split) )
self.assertEqual( 0, self.trans.GetSplitIndex(self.split) )
def test_countsplits(self):
self.assertEquals( 1, self.trans.CountSplits() )
self.assertEqual( 1, self.trans.CountSplits() )
def test_readonly(self):
self.assertEquals( None, self.trans.GetReadOnly() )
self.assertEqual( None, self.trans.GetReadOnly() )
REASON = 'none'
self.trans.SetReadOnly(REASON)
self.assertEquals( REASON, self.trans.GetReadOnly() )
self.assertEqual( REASON, self.trans.GetReadOnly() )
self.trans.ClearReadOnly()
self.assertEquals( None, self.trans.GetReadOnly() )
self.assertEqual( None, self.trans.GetReadOnly() )
def test_txntype(self):
self.assertEquals( '\x00', self.trans.GetTxnType() )
self.assertEqual( '\x00', self.trans.GetTxnType() )
TYPE = 'I'
self.trans.SetTxnType(TYPE)
self.assertEquals( TYPE, self.trans.GetTxnType() )
self.assertEqual( TYPE, self.trans.GetTxnType() )
TYPE = 'P'
self.trans.SetTxnType(TYPE)
self.assertEquals( TYPE, self.trans.GetTxnType() )
self.assertEqual( TYPE, self.trans.GetTxnType() )
def test_num(self):
NUM = '5'
self.assertEquals( '', self.trans.GetNum() )
self.assertEqual( '', self.trans.GetNum() )
self.trans.SetNum(NUM)
self.assertEquals( NUM, self.trans.GetNum() )
self.assertEqual( NUM, self.trans.GetNum() )
def test_description(self):
DESCR = 'Groceries'
self.assertEquals( '', self.trans.GetDescription() )
self.assertEqual( '', self.trans.GetDescription() )
self.trans.SetDescription(DESCR)
self.assertEquals( DESCR, self.trans.GetDescription() )
self.assertEqual( DESCR, self.trans.GetDescription() )
def test_notes(self):
NOTE = 'For dinner party'
self.assertEquals( None, self.trans.GetNotes() )
self.assertEqual( None, self.trans.GetNotes() )
self.trans.SetNotes(NOTE)
self.assertEquals( NOTE, self.trans.GetNotes() )
self.assertEqual( NOTE, self.trans.GetNotes() )
if __name__ == '__main__':
main()

View File

@ -75,7 +75,7 @@ macro (gnc_add_swig_python_command _target _out_var _py_out_var _output _py_outp
if (GENERATE_SWIG_WRAPPERS)
set (DEFAULT_SWIG_PYTHON_FLAGS
-python
-python -py3
-Wall -Werror
${SWIG_ARGS}
)

View File

@ -73,7 +73,11 @@ libgncmod_python_gnc_module_init(int refcount)
*/
FILE *fp;
gchar *pkgdatadir, *init_filename;
#if PY_VERSION_HEX >= 0x03000000
wchar_t* argv = NULL;
#else
char* argv = "";
#endif
Py_Initialize();
PySys_SetArgv(0, &argv);
#if PY_VERSION_HEX >= 0x03000000