diff --git a/bindings/python/example_scripts/str_methods.py b/bindings/python/example_scripts/str_methods.py index dfbec36c66..26bf5bc573 100644 --- a/bindings/python/example_scripts/str_methods.py +++ b/bindings/python/example_scripts/str_methods.py @@ -65,7 +65,7 @@ def ya_add_method(_class, function, method_name=None, clsmethod=False, noinstanc setattr(gnucash.gnucash_core_c,function.__name__,function) if clsmethod: - mf=_class.ya_add_classmethod(function.__name__,method_name) + mf=_class.add_classmethod(function.__name__,method_name) elif noinstance: mf=_class.add_method(function.__name__,method_name) else: diff --git a/bindings/python/function_class.py b/bindings/python/function_class.py index 6bc954f1ba..6cb5ac1866 100644 --- a/bindings/python/function_class.py +++ b/bindings/python/function_class.py @@ -87,7 +87,7 @@ class ClassFromFunctions(object): @classmethod def add_method(cls, function_name, method_name): - """! Add the function, method_name to this class as a method named name + """! Add the function, function_name to this class as a method named method_name arguments: @param cls Class: class to add methods to @@ -116,8 +116,8 @@ class ClassFromFunctions(object): return method_function @classmethod - def ya_add_classmethod(cls, function_name, method_name): - """! Add the function, method_name to this class as a classmethod named name + def add_classmethod(cls, function_name, method_name): + """! Add the function, function_name to this class as a classmethod named method_name Taken from function_class and modified from add_method() to add classmethod instead of method and not to turn self argument to self.instance. @@ -129,17 +129,16 @@ class ClassFromFunctions(object): function will be wrapped by method_function""" - def method_function(self, *meth_func_args, **meth_func_kargs): + def method_function(cls, *meth_func_args, **meth_func_kargs): """! wrapper method for function arguments: - @param self: FunctionClass instance. + @param cls: FunctionClass. @param *meth_func_args: arguments to be passed to function. All FunctionClass objects will be turned to their respective instances. @param **meth_func_kargs: keyword arguments to be passed to function. All FunctionClass objects will be turned to their respective instances.""" - return getattr(self._module, function_name)( - self, + return getattr(cls._module, function_name)( *process_list_convert_to_instance(meth_func_args), **process_dict_convert_to_instance(meth_func_kargs) ) @@ -240,6 +239,26 @@ def method_function_returns_instance(method_function, cls): return new_function +def classmethod_function_returns_instance(method_function, cls): + """A function decorator that is used to decorate classmethod functions that + return instance data, to return instances instead. + + You can't use this decorator with @, because this function has a second + argument. + """ + assert( 'instance' == INSTANCE_ARGUMENT ) + # Will get a class in arguement list, + # use static so we don't add another here. + @staticmethod + def new_function(*args, **kargs): + kargs_cls = { INSTANCE_ARGUMENT : method_function(*args, **kargs) } + if kargs_cls['instance'] == None: + return None + else: + return cls( **kargs_cls ) + + return new_function + def method_function_returns_instance_list(method_function, cls): def new_function(*args, **kargs): return [ cls( **{INSTANCE_ARGUMENT: item} ) diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py index 993ad8919b..34482ebf90 100644 --- a/bindings/python/gnucash_core.py +++ b/bindings/python/gnucash_core.py @@ -169,7 +169,8 @@ 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 + method_function_returns_instance_list, methods_return_instance_lists, \ + classmethod_function_returns_instance from gnucash.gnucash_core_c import gncInvoiceLookup, gncInvoiceGetInvoiceFromTxn, \ gncInvoiceGetInvoiceFromLot, gncEntryLookup, gncInvoiceLookup, \ @@ -1007,6 +1008,8 @@ GncCommodityNamespace.get_commodity_list = \ # GncLot GncLot.add_constructor_and_methods_with_prefix('gnc_lot_', 'new') +# replace method for gnc_lot_make_default() to be a classmethod +GncLot.add_classmethod('gnc_lot_make_default', 'make_default') gnclot_dict = { 'get_account' : Account, @@ -1014,10 +1017,13 @@ gnclot_dict = { 'get_earliest_split' : Split, 'get_latest_split' : Split, 'get_balance' : GncNumeric, - 'lookup' : GncLot, - 'make_default' : GncLot + 'lookup' : GncLot } methods_return_instance(GncLot, gnclot_dict) +GncLot.make_default = classmethod_function_returns_instance(GncLot.make_default, GncLot) +methods_return_instance_lists( + GncLot, { 'get_split_list': Split + }) # Transaction Transaction.add_methods_with_prefix('xaccTrans') @@ -1057,6 +1063,7 @@ split_dict = { 'GetBook': Book, 'GetAccount': Account, 'GetParent': Transaction, + 'GetLot': GncLot, 'Lookup': Split, 'GetOtherSplit': Split, 'GetAmount': GncNumeric, @@ -1069,7 +1076,8 @@ split_dict = { 'GetReconciledBalance': GncNumeric, 'VoidFormerAmount': GncNumeric, 'VoidFormerValue': GncNumeric, - 'GetGUID': GUID + 'GetGUID': GUID, + 'AssignToLot': Split } methods_return_instance(Split, split_dict) @@ -1114,6 +1122,7 @@ account_dict = { methods_return_instance(Account, account_dict) methods_return_instance_lists( Account, { 'GetSplitList': Split, + 'GetLotList': GncLot, 'get_children': Account, 'get_children_sorted': Account, 'get_descendants': Account, diff --git a/bindings/python/tests/runTests.py.in b/bindings/python/tests/runTests.py.in index 5b9142592d..d351ed443c 100755 --- a/bindings/python/tests/runTests.py.in +++ b/bindings/python/tests/runTests.py.in @@ -16,6 +16,7 @@ from test_business import TestBusiness from test_commodity import TestCommodity, TestCommodityNamespace from test_numeric import TestGncNumeric from test_query import TestQuery +from test_lot import TestLot if __name__ == '__main__': unittest.main() diff --git a/bindings/python/tests/test_function_class.py b/bindings/python/tests/test_function_class.py index df099e086b..dea8f16599 100644 --- a/bindings/python/tests/test_function_class.py +++ b/bindings/python/tests/test_function_class.py @@ -41,6 +41,10 @@ def other_function(self, arg=None): return self, arg +def class_other_function(arg=None): + return arg + + class TestClass(ClassFromFunctions): _module = sys.modules[__name__] @@ -80,6 +84,17 @@ class TestFunctionClass(TestCase): obj, arg = self.t.other_method(arg=self.t) self.assertIsInstance(arg, Instance) + def test_add_classmethod(self): + """test if add_classmethod adds method and if in case of FunctionClass + Instance instances get returned instead of FunctionClass instances""" + TestClass.add_constructor_and_methods_with_prefix("prefix_", "new_function") + TestClass.add_classmethod("class_other_function", "other_method") + self.t = TestClass() + arg = TestClass.other_method(self.t) + self.assertIsInstance(arg, Instance) + arg = TestClass.other_method(arg=self.t) + self.assertIsInstance(arg, Instance) + def test_default_arguments_decorator(self): """test default_arguments_decorator()""" TestClass.backup_test_function_return_args = TestClass.test_function_return_args diff --git a/bindings/python/tests/test_lot.py b/bindings/python/tests/test_lot.py new file mode 100644 index 0000000000..961f6dca2b --- /dev/null +++ b/bindings/python/tests/test_lot.py @@ -0,0 +1,64 @@ +from unittest import main +from gnucash import Book, Account, GncLot, Split, GncNumeric + +from test_account import AccountSession +# from test_split import SplitSession + +class LotSession(AccountSession): + def setUp(self): + AccountSession.setUp(self) + self.NUM = 10000 + self.amount = GncNumeric(self.NUM, 100) + + def setup_buysplit(self): + self.buysplit = Split(self.book) + self.buysplit.SetAccount(self.account) + self.buysplit.SetAmount(self.amount) + + def setup_sellsplit(self): + self.sellsplit = Split(self.book) + self.sellsplit.SetAccount(self.account) + self.sellsplit.SetAmount(self.amount.neg()) + +class TestLot(LotSession): + def test_make_default(self): + self.lot = GncLot.make_default(self.account) + self.assertIsInstance(self.lot, GncLot) + + def test_AssignToLot(self): + self.lot = GncLot.make_default(self.account) + + self.setup_buysplit() + self.buysplit.AssignToLot(self.lot) + self.assertEqual(self.NUM, self.lot.get_balance().num()) + self.assertTrue(not self.lot.is_closed()) + + self.setup_sellsplit() + self.sellsplit.AssignToLot(self.lot) + self.assertEqual(0, self.lot.get_balance().num()) + self.assertTrue(self.lot.is_closed()) + + def test_Split_GetLot(self): + self.lot = GncLot.make_default(self.account) + self.setup_buysplit() + self.buysplit.AssignToLot(self.lot) + rtn_lot = self.buysplit.GetLot() + self.assertEqual(rtn_lot.get_title(), self.lot.get_title()) + + def test_get_split_list(self): + self.lot = GncLot.make_default(self.account) + self.setup_buysplit() + self.buysplit.AssignToLot(self.lot) + splits = self.lot.get_split_list() + self.assertEqual(self.NUM, splits[0].GetAmount().num()) + self.assertEqual(self.account.name, splits[0].GetAccount().name) + + def test_Account_GetLotList(self): + self.lot = GncLot.make_default(self.account) + self.setup_buysplit() + self.buysplit.AssignToLot(self.lot) + lots = self.account.GetLotList() + self.assertEqual(self.account.name, lots[0].get_account().name) + +if __name__ == '__main__': + unittest.main() diff --git a/common/base-typemaps.i b/common/base-typemaps.i index 2a878a0122..b5a053f5e0 100644 --- a/common/base-typemaps.i +++ b/common/base-typemaps.i @@ -331,5 +331,8 @@ typedef char gchar; PyList_Append(list, SWIG_NewPointerObj(data, SWIGTYPE_p_void, 0)); } $result = list; + if ($1_descriptor == $descriptor(AccountList *) || + $1_descriptor == $descriptor(LotList *)) + g_list_free($1); } #endif