gnucash unstable: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Tue Nov 28 20:09:58 EST 2017
Updated via https://github.com/Gnucash/gnucash/commit/c6ae007b (commit)
via https://github.com/Gnucash/gnucash/commit/744cdac5 (commit)
via https://github.com/Gnucash/gnucash/commit/c9c58764 (commit)
via https://github.com/Gnucash/gnucash/commit/e011576e (commit)
via https://github.com/Gnucash/gnucash/commit/1ef379a7 (commit)
from https://github.com/Gnucash/gnucash/commit/5aa048e0 (commit)
commit c6ae007bda0e3a4b3eaeb3e55193b150f6472461
Merge: 5aa048e 744cdac
Author: John Ralls <jralls at ceridwen.us>
Date: Tue Nov 28 17:09:38 2017 -0800
Merge branch 'PyGncNumeric' into unstable
commit 744cdac5a4dfb3e62f5d447d4f71be872f182c88
Author: Guy Taylor <thebigguy.co.uk at gmail.com>
Date: Sat Jun 3 17:51:54 2017 +0100
Use builtin SWIG conversions for glib types
Where possible in the Python SWIG code use the builtin SWIG conversion
code over custom code. This ensures appropriate overflow/type checking.
With this I have enabled GncNumeric from longs and tested for correct
overflow handling.
Note: This could be extended to GUILE but I am not familiar enought to
safely enable this.
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 6fc9a62..e3c1dd7 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -294,7 +294,7 @@ class GncNumeric(GnuCashCoreClass):
return gnc_numeric_zero()
elif len(args) == 1:
arg = args[0]
- if type(arg) == int:
+ if type(arg) in (int, long):
return gnc_numeric_create(arg ,1)
elif type(arg) == float:
return double_to_gnc_numeric(arg, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER)
diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt
index ef38aa1..201bde7 100644
--- a/bindings/python/tests/CMakeLists.txt
+++ b/bindings/python/tests/CMakeLists.txt
@@ -13,6 +13,7 @@ SET(test_python_bindings_DATA
test_book.py
test_business.py
test_commodity.py
+ test_numeric.py
test_split.py
test_transaction.py)
diff --git a/bindings/python/tests/Makefile.am b/bindings/python/tests/Makefile.am
index d8ac3eb..62c50cb 100644
--- a/bindings/python/tests/Makefile.am
+++ b/bindings/python/tests/Makefile.am
@@ -48,6 +48,8 @@ EXTRA_DIST = \
test_account.py \
test_book.py \
test_split.py \
+ test_commodity.py \
+ test_numeric.py \
test_transaction.py \
test_business.py \
CMakeLists.txt
diff --git a/bindings/python/tests/test_numeric.py b/bindings/python/tests/test_numeric.py
new file mode 100644
index 0000000..2c367eb
--- /dev/null
+++ b/bindings/python/tests/test_numeric.py
@@ -0,0 +1,106 @@
+from unittest import TestCase, main
+
+from gnucash import GncNumeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED, \
+ GNC_HOW_RND_NEVER, GNC_HOW_RND_FLOOR, GNC_HOW_RND_CEIL
+
+class TestGncNumeric( TestCase ):
+ def test_defaut(self):
+ num = GncNumeric()
+ self.assertEqual(str(num), "0/1")
+ self.assertEqual(num.num(), 0)
+ self.assertEqual(num.denom(), 1)
+
+ def test_from_num_denom(self):
+ num = GncNumeric(1, 2)
+ self.assertEqual(str(num), "1/2")
+ self.assertEqual(num.num(), 1)
+ self.assertEqual(num.denom(), 2)
+
+ def test_from_int(self):
+ num = GncNumeric(3)
+ self.assertEqual(str(num), "3/1")
+ 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)
+
+ #One might think this would be an overflow error, but SWIG type-checks
+ #it first and discovers that it's too big to be an int64_t.
+ with self.assertRaises(TypeError):
+ GncNumeric((2**64)+1)
+
+ def test_from_float(self):
+ num = GncNumeric(3.1, 20, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER)
+ self.assertEqual(str(num), "62/20")
+ self.assertEqual(num.num(), 62)
+ self.assertEqual(num.denom(), 20)
+
+ num = GncNumeric(1/3.0, 10000000000, GNC_HOW_RND_FLOOR)
+ self.assertEqual(str(num), "3333333333/10000000000")
+ self.assertEqual(num.num(), 3333333333)
+ self.assertEqual(num.denom(), 10000000000)
+
+ num = GncNumeric(1/3.0, 10000000000, GNC_HOW_RND_CEIL)
+ self.assertEqual(str(num), "3333333334/10000000000")
+ self.assertEqual(num.num(), 3333333334)
+ self.assertEqual(num.denom(), 10000000000)
+
+ def test_from_float_auto(self):
+ num = GncNumeric(3.1)
+ self.assertEqual(str(num), "31/10")
+ self.assertEqual(num.num(), 31)
+ self.assertEqual(num.denom(), 10)
+
+ def test_from_instance(self):
+ orig = GncNumeric(3)
+ num = GncNumeric(instance=orig.instance)
+ self.assertEqual(str(num), "3/1")
+ self.assertEqual(num.num(), 3)
+ self.assertEqual(num.denom(), 1)
+
+ def test_from_str(self):
+ num = GncNumeric("3.1")
+ self.assertEqual(str(num), "31/10")
+ self.assertEqual(num.num(), 31)
+ self.assertEqual(num.denom(), 10)
+
+ num = GncNumeric("1/3")
+ self.assertEqual(str(num), "1/3")
+ self.assertEqual(num.num(), 1)
+ self.assertEqual(num.denom(), 3)
+
+ def test_to_str(self):
+ num = GncNumeric("1000/3")
+ self.assertEqual(str(num), "1000/3")
+
+ num = GncNumeric(1, 0)
+ self.assertEqual(str(num), "1/0")
+
+ def test_to_double(self):
+ for test_num in [0.0, 1.1, -1.1, 1/3.0]:
+ self.assertEqual(GncNumeric(test_num).to_double(), test_num)
+
+ def test_to_fraction(self):
+ fraction = GncNumeric("1000/3").to_fraction()
+ self.assertEqual(fraction.numerator, 1000)
+ self.assertEqual(fraction.denominator, 3)
+
+ def test_incorect_args(self):
+ with self.assertRaises(TypeError):
+ GncNumeric(1, 2, 3)
+
+ with self.assertRaises(TypeError):
+ GncNumeric("1", 2)
+
+ with self.assertRaises(TypeError):
+ GncNumeric(1.1, "round")
+
+ with self.assertRaises(TypeError):
+ GncNumeric(complex(1, 1))
+
+if __name__ == '__main__':
+ main()
diff --git a/common/base-typemaps.i b/common/base-typemaps.i
index 728186f..62c8f96 100644
--- a/common/base-typemaps.i
+++ b/common/base-typemaps.i
@@ -169,38 +169,16 @@ typedef char gchar;
#elif defined(SWIGPYTHON) /* Typemaps for Python */
%import "glib.h"
-
-%typemap(in) gint8, gint16, gint32, gint64, gshort, glong {
- $1 = ($1_type)PyInt_AsLong($input);
-}
-
-%typemap(out) gint8, gint16, gint32, gint64, gshort, glong {
- $result = PyInt_FromLong($1);
-}
-
-%typemap(in) guint8, guint16, guint32, guint64, gushort, gulong {
- $1 = ($1_type)PyLong_AsUnsignedLong($input);
-}
-
-%typemap(out) guint8, guint16, guint32, guint64, gushort, gulong {
- $result = PyLong_FromUnsignedLong($1);
-}
-
-%typemap(in) gdouble {
- $1 = ($1_type)PyFloat_AsDouble($input);
-}
-
-%typemap(out) gdouble {
- $result = PyFloat_FromDouble($1);
-}
-
-%typemap(in) gchar * {
- $1 = ($1_ltype)PyString_AsString($input);
-}
-
-%typemap(out) gchar * {
- $result = PyString_FromString($1);
-}
+%include <stdint.i>
+
+%apply int { gint };
+%apply unsigned int { guint };
+%apply long { glong };
+%apply int64_t { gint64 };
+%apply unsigned long { gulong };
+%apply uint64_t { guint64 };
+%apply double { gdouble };
+%apply char* { gchar* };
%typemap(in) gboolean {
if ($input == Py_True)
diff --git a/common/cmake_modules/GncAddSwigCommand.cmake b/common/cmake_modules/GncAddSwigCommand.cmake
index 7671aa9..1fdcaa5 100644
--- a/common/cmake_modules/GncAddSwigCommand.cmake
+++ b/common/cmake_modules/GncAddSwigCommand.cmake
@@ -26,10 +26,9 @@ MACRO (GNC_ADD_SWIG_PYTHON_COMMAND _target _output _input)
)
set (DEFAULT_SWIG_PYTHON_C_INCLUDES
${GLIB2_INCLUDE_DIRS}
- ${CMAKE_SOURCE_DIR}/src/libqof/qof
- ${CMAKE_SOURCE_DIR}/src
- ${CMAKE_SOURCE_DIR}/src/engine
- ${CMAKE_SOURCE_DIR}/src/app-utils
+ ${CMAKE_SOURCE_DIR}/common
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine
+ ${CMAKE_SOURCE_DIR}/libgnucash/app-utils
)
commit c9c58764318ab7e82ac21958326a64a27f6d409a
Author: Guy Taylor <thebigguy.co.uk at gmail.com>
Date: Sat Jun 3 16:06:09 2017 +0100
Use glib.h over custom typedefs in Python SWIG
Use the native glib.h (mainly gint, gfloat ...) over custom typedefs in
SWIG type files. This is for Python only.
diff --git a/common/base-typemaps.i b/common/base-typemaps.i
index 187b64e..728186f 100644
--- a/common/base-typemaps.i
+++ b/common/base-typemaps.i
@@ -22,18 +22,16 @@
* *
\********************************************************************/
+typedef void * gpointer; // Not sure why SWIG doesn't figure this out.
+%typemap(newfree) gchar * "g_free($1);"
+
+#if defined(SWIGGUILE)
-/* Not sure why SWIG doesn't figure this out. */
typedef int gint;
typedef gint64 time64;
typedef unsigned int guint;
typedef double gdouble;
typedef float gfloat;
-typedef void * gpointer;
-
-%typemap(newfree) gchar * "g_free($1);"
-
-#if defined(SWIGGUILE)
typedef char gchar;
%typemap (out) char * {
@@ -169,6 +167,9 @@ typedef char gchar;
}
%enddef
#elif defined(SWIGPYTHON) /* Typemaps for Python */
+
+%import "glib.h"
+
%typemap(in) gint8, gint16, gint32, gint64, gshort, glong {
$1 = ($1_type)PyInt_AsLong($input);
}
@@ -185,6 +186,14 @@ typedef char gchar;
$result = PyLong_FromUnsignedLong($1);
}
+%typemap(in) gdouble {
+ $1 = ($1_type)PyFloat_AsDouble($input);
+}
+
+%typemap(out) gdouble {
+ $result = PyFloat_FromDouble($1);
+}
+
%typemap(in) gchar * {
$1 = ($1_ltype)PyString_AsString($input);
}
diff --git a/common/cmake_modules/GncAddSwigCommand.cmake b/common/cmake_modules/GncAddSwigCommand.cmake
index 440b6b9..7671aa9 100644
--- a/common/cmake_modules/GncAddSwigCommand.cmake
+++ b/common/cmake_modules/GncAddSwigCommand.cmake
@@ -19,12 +19,26 @@ ENDMACRO (GNC_ADD_SWIG_COMMAND)
MACRO (GNC_ADD_SWIG_PYTHON_COMMAND _target _output _input)
- ADD_CUSTOM_COMMAND(OUTPUT ${_output}
+ set (DEFAULT_SWIG_PYTHON_FLAGS
+ -python
+ -Wall -Werror
+ ${SWIG_ARGS}
+ )
+ set (DEFAULT_SWIG_PYTHON_C_INCLUDES
+ ${GLIB2_INCLUDE_DIRS}
+ ${CMAKE_SOURCE_DIR}/src/libqof/qof
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_SOURCE_DIR}/src/engine
+ ${CMAKE_SOURCE_DIR}/src/app-utils
+ )
- COMMAND ${SWIG_EXECUTABLE} -python -Wall -Werror ${SWIG_ARGS}
- -I${CMAKE_SOURCE_DIR}/common
- -I${CMAKE_SOURCE_DIR}/libgnucash/engine -I${CMAKE_SOURCE_DIR}/libgnucash/app-utils
- -o ${_output} ${_input}
+
+ set (PYTHON_SWIG_FLAGS ${DEFAULT_SWIG_PYTHON_FLAGS})
+ foreach (dir ${DEFAULT_SWIG_PYTHON_C_INCLUDES})
+ list (APPEND PYTHON_SWIG_FLAGS "-I${dir}")
+ endforeach (dir)
+ ADD_CUSTOM_COMMAND(OUTPUT ${_output}
+ COMMAND ${SWIG_EXECUTABLE} ${PYTHON_SWIG_FLAGS} -o ${_output} ${_input}
DEPENDS ${_input} ${CMAKE_SOURCE_DIR}/common/base-typemaps.i ${ARGN}
)
ADD_CUSTOM_TARGET(${_target} ALL DEPENDS ${_output} ${CMAKE_SOURCE_DIR}/common/base-typemaps.i ${_input} ${ARGN})
diff --git a/common/test-core/Makefile.am b/common/test-core/Makefile.am
index 2d20337..0aa560b 100644
--- a/common/test-core/Makefile.am
+++ b/common/test-core/Makefile.am
@@ -38,7 +38,7 @@ endif
swig-unittest-support-python.c: unittest-support.i $(top_srcdir)/common/base-typemaps.i
$(SWIG) -python -Wall -Werror $(SWIG_ARGS) \
- -I${top_srcdir}/common \
+ -I${GLIB_CFLAGS} -I${top_srcdir}/common \
${AM_CPPFLAGS} -o $@ $<
unittest-support.py: swig-unittest-support-python.c ${SWIG_FILES}
diff --git a/libgnucash/app-utils/Makefile.am b/libgnucash/app-utils/Makefile.am
index 6296bb2..4200b56 100644
--- a/libgnucash/app-utils/Makefile.am
+++ b/libgnucash/app-utils/Makefile.am
@@ -114,7 +114,7 @@ endif
endif
swig-app-utils-python.c: app-utils.i ${top_srcdir}/common/base-typemaps.i
$(SWIG) -python -Wall -Werror $(SWIG_ARGS) \
- -I${top_srcdir}/common -o $@ $<
+ -I${GLIB_CFLAGS} -I${top_srcdir}/common -o $@ $<
endif
if WITH_PYTHON
diff --git a/libgnucash/core-utils/Makefile.am b/libgnucash/core-utils/Makefile.am
index fdd2ebe..c71072c 100644
--- a/libgnucash/core-utils/Makefile.am
+++ b/libgnucash/core-utils/Makefile.am
@@ -51,7 +51,7 @@ endif
endif
swig-core-utils-python.c: core-utils.i ${top_srcdir}/common/base-typemaps.i
$(SWIG) -python -Wall -Werror $(SWIG_ARGS) \
- -I${top_srcdir}/common -o $@ $<
+ -I${GLIB_CFLAGS} -I${top_srcdir}/common -o $@ $<
endif
AM_CPPFLAGS = \
diff --git a/src/optional/python-bindings/tests/test_numeric.py b/src/optional/python-bindings/tests/test_numeric.py
deleted file mode 100644
index abb0278..0000000
--- a/src/optional/python-bindings/tests/test_numeric.py
+++ /dev/null
@@ -1,101 +0,0 @@
-from unittest import TestCase, main
-
-from gnucash import GncNumeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED, \
- GNC_HOW_RND_NEVER, GNC_HOW_RND_FLOOR, GNC_HOW_RND_CEIL
-
-class TestGncNumeric( TestCase ):
- def test_defaut(self):
- num = GncNumeric()
- self.assertEqual(str(num), "0/1")
- self.assertEqual(num.num(), 0)
- self.assertEqual(num.denom(), 1)
-
- def test_from_num_denom(self):
- num = GncNumeric(1, 2)
- self.assertEqual(str(num), "1/2")
- self.assertEqual(num.num(), 1)
- self.assertEqual(num.denom(), 2)
-
- def test_from_int(self):
- num = GncNumeric(3)
- self.assertEqual(str(num), "3/1")
- self.assertEqual(num.num(), 3)
- self.assertEqual(num.denom(), 1)
-
- # Safest outcome at current. This can be fixed but correct bounds checks
- # are required to ensure gint64 is not overflowed.
- def test_from_long(self):
- with self.assertRaises(TypeError):
- GncNumeric(3L)
-
- def test_from_float(self):
- num = GncNumeric(3.1, 20, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER)
- self.assertEqual(str(num), "62/20")
- self.assertEqual(num.num(), 62)
- self.assertEqual(num.denom(), 20)
-
- num = GncNumeric(1/3.0, 10000000000, GNC_HOW_RND_FLOOR)
- self.assertEqual(str(num), "3333333333/10000000000")
- self.assertEqual(num.num(), 3333333333)
- self.assertEqual(num.denom(), 10000000000)
-
- num = GncNumeric(1/3.0, 10000000000, GNC_HOW_RND_CEIL)
- self.assertEqual(str(num), "3333333334/10000000000")
- self.assertEqual(num.num(), 3333333334)
- self.assertEqual(num.denom(), 10000000000)
-
- def test_from_float_auto(self):
- num = GncNumeric(3.1)
- self.assertEqual(str(num), "31/10")
- self.assertEqual(num.num(), 31)
- self.assertEqual(num.denom(), 10)
-
- def test_from_instance(self):
- orig = GncNumeric(3)
- num = GncNumeric(instance=orig.instance)
- self.assertEqual(str(num), "3/1")
- self.assertEqual(num.num(), 3)
- self.assertEqual(num.denom(), 1)
-
- def test_from_str(self):
- num = GncNumeric("3.1")
- self.assertEqual(str(num), "31/10")
- self.assertEqual(num.num(), 31)
- self.assertEqual(num.denom(), 10)
-
- num = GncNumeric("1/3")
- self.assertEqual(str(num), "1/3")
- self.assertEqual(num.num(), 1)
- self.assertEqual(num.denom(), 3)
-
- def test_to_str(self):
- num = GncNumeric("1000/3")
- self.assertEqual(str(num), "1000/3")
-
- num = GncNumeric(1, 0)
- self.assertEqual(str(num), "1/0")
-
- def test_to_double(self):
- for test_num in [0.0, 1.1, -1.1, 1/3.0]:
- self.assertEqual(GncNumeric(test_num).to_double(), test_num)
-
- def test_to_fraction(self):
- fraction = GncNumeric("1000/3").to_fraction()
- self.assertEqual(fraction.numerator, 1000)
- self.assertEqual(fraction.denominator, 3)
-
- def test_incorect_args(self):
- with self.assertRaises(TypeError):
- GncNumeric(1, 2, 3)
-
- with self.assertRaises(TypeError):
- GncNumeric("1", 2)
-
- with self.assertRaises(TypeError):
- GncNumeric(1.1, "round")
-
- with self.assertRaises(TypeError):
- GncNumeric(complex(1, 1))
-
-if __name__ == '__main__':
- main()
commit e011576e37e25c2876c6ed8578a8ccc2573573f1
Author: Guy Taylor <thebigguy.co.uk at gmail.com>
Date: Sun May 28 22:13:39 2017 +0100
Add GncNumeric to native Python Fraction
Add helper method to return the native Python fraction type from GncNumeric.
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 76d838e..6fc9a62 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -320,6 +320,10 @@ class GncNumeric(GnuCashCoreClass):
else:
raise TypeError('Required single int/float/str or two ints: ' + str(args))
+ def to_fraction(self):
+ from fractions import Fraction
+ return Fraction(self.num(), self.denom())
+
def __unicode__(self):
"""Returns a human readable numeric value string as UTF8."""
return gnc_numeric_to_string(self.instance)
diff --git a/src/optional/python-bindings/tests/test_numeric.py b/src/optional/python-bindings/tests/test_numeric.py
index 3d8f9da..abb0278 100644
--- a/src/optional/python-bindings/tests/test_numeric.py
+++ b/src/optional/python-bindings/tests/test_numeric.py
@@ -79,6 +79,11 @@ class TestGncNumeric( TestCase ):
for test_num in [0.0, 1.1, -1.1, 1/3.0]:
self.assertEqual(GncNumeric(test_num).to_double(), test_num)
+ def test_to_fraction(self):
+ fraction = GncNumeric("1000/3").to_fraction()
+ self.assertEqual(fraction.numerator, 1000)
+ self.assertEqual(fraction.denominator, 3)
+
def test_incorect_args(self):
with self.assertRaises(TypeError):
GncNumeric(1, 2, 3)
commit 1ef379a704e070a174eebb894487a230008d9977
Author: Guy Taylor <thebigguy.co.uk at gmail.com>
Date: Wed May 17 11:37:20 2017 +0100
Fix Python GncNumeric for non (int, int) pairs
At current the Python GncNumeric has issues with type conversion eg.
* GncNumeric(1.3) = 1.00
* GncNumeric("1.3") is OK but any future methods error
This behaviour was relied on for the Account tests to pass as it used
GncNumeric(0.5) == GncNumeric(1.0) but this is not what many users would
expect.
This fix alows GncNumeric to be constructed from a (int, int)
numerator/denominator pair or int/float/str where double_to_gnc_numeric
and string_to_gnc_numeric from C is used.
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index b72ecb1..76d838e 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -42,7 +42,9 @@ from gnucash_core_c import gncInvoiceLookup, gncInvoiceGetInvoiceFromTxn, \
gncTaxTableLookup, gncTaxTableLookupByName, gnc_search_invoice_on_id, \
gnc_search_customer_on_id, gnc_search_bill_on_id , \
gnc_search_vendor_on_id, gncInvoiceNextID, gncCustomerNextID, \
- gncVendorNextID, gncTaxTableGetTables
+ gncVendorNextID, gncTaxTableGetTables, gnc_numeric_zero, \
+ gnc_numeric_create, double_to_gnc_numeric, string_to_gnc_numeric, \
+ gnc_numeric_to_string
class GnuCashCoreClass(ClassFromFunctions):
_module = gnucash_core_c
@@ -271,26 +273,56 @@ class GncNumeric(GnuCashCoreClass):
Look at gnc-numeric.h to see how to use these
"""
- def __init__(self, num=0, denom=1, **kargs):
- """Constructor that allows you to set the numerator and denominator or
- leave them blank with a default value of 0 (not a good idea since there
- is currently no way to alter the value after instantiation)
+ def __init__(self, *args, **kargs):
+ """Constructor that supports the following formats:
+ * No arguments defaulting to zero: eg. GncNumeric() == 0/1
+ * A integer: e.g. GncNumeric(1) == 1/1
+ * Numerator and denominator intager pair: eg. GncNumeric(1, 2) == 1/2
+ * A floating point number: e.g. GncNumeric(0.5) == 1/2
+ * A floating point number with defined conversion: e.g.
+ GncNumeric(0.5, GNC_DENOM_AUTO,
+ GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER) == 1/2
+ * A string: e.g. GncNumeric("1/2") == 1/2
"""
- GnuCashCoreClass.__init__(self, num, denom, **kargs)
- #if INSTANCE_ARG in kargs:
- # GnuCashCoreClass.__init__(**kargs)
- #else:
- # self.set_denom(denom) # currently undefined
- # self.set_num(num) # currently undefined
+ if 'instance' not in kargs:
+ kargs['instance'] = GncNumeric.__args_to_instance(args)
+ GnuCashCoreClass.__init__(self, [], **kargs)
+
+ @staticmethod
+ def __args_to_instance(args):
+ if len(args) == 0:
+ return gnc_numeric_zero()
+ elif len(args) == 1:
+ arg = args[0]
+ if type(arg) == int:
+ return gnc_numeric_create(arg ,1)
+ elif type(arg) == float:
+ return double_to_gnc_numeric(arg, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER)
+ elif type(arg) == str:
+ instance = gnc_numeric_zero()
+ if not string_to_gnc_numeric(arg, instance):
+ raise TypeError('Failed to convert to GncNumeric: ' + str(args))
+ return instance
+ 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:
+ 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]) == type(GNC_DENOM_AUTO) \
+ and type(args[2]) == type(GNC_HOW_DENOM_FIXED):
+ return double_to_gnc_numeric(*args)
+ else:
+ raise TypeError('Only (float, GNC_HOW_RND_*, GNC_HOW_RND_*, GNC_HOW_RND_*) allowed: ' + str(args))
+ else:
+ raise TypeError('Required single int/float/str or two ints: ' + str(args))
def __unicode__(self):
"""Returns a human readable numeric value string as UTF8."""
- if self.denom() == 0:
- return "Division by zero"
- else:
- value_float = self.to_double()
- value_str = u"{0:.{1}f}".format(value_float,2) ## The second argument is the precision. It would be nice to be able to make it configurable.
- return value_str
+ return gnc_numeric_to_string(self.instance)
def __str__(self):
"""returns a human readable numeric value string as bytes."""
diff --git a/bindings/python/tests/runTests.py.in b/bindings/python/tests/runTests.py.in
index 0a1d269..60d040f 100755
--- a/bindings/python/tests/runTests.py.in
+++ b/bindings/python/tests/runTests.py.in
@@ -13,9 +13,10 @@ from test_split import TestSplit
from test_transaction import TestTransaction
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)
+ test_support.run_unittest(TestBook, TestAccount, TestSplit, TestTransaction, TestBusiness, TestCommodity, TestCommodityNamespace, TestGncNumeric)
if __name__ == '__main__':
test_main()
diff --git a/bindings/python/tests/test_account.py b/bindings/python/tests/test_account.py
index b05f171..5ba972e 100644
--- a/bindings/python/tests/test_account.py
+++ b/bindings/python/tests/test_account.py
@@ -40,7 +40,7 @@ class TestAccount( AccountSession ):
s1a = Split(self.book)
s1a.SetParent(tx)
s1a.SetAccount(self.account)
- s1a.SetAmount(GncNumeric(1.0))
+ s1a.SetAmount(GncNumeric(1.3))
s1a.SetValue(GncNumeric(100.0))
s1b = Split(self.book)
@@ -52,7 +52,7 @@ class TestAccount( AccountSession ):
s2a = Split(self.book)
s2a.SetParent(tx)
s2a.SetAccount(self.account)
- s2a.SetAmount(GncNumeric(-0.5))
+ s2a.SetAmount(GncNumeric(-1.3))
s2a.SetValue(GncNumeric(-100.0))
s2b = Split(self.book)
diff --git a/src/optional/python-bindings/tests/test_numeric.py b/src/optional/python-bindings/tests/test_numeric.py
new file mode 100644
index 0000000..3d8f9da
--- /dev/null
+++ b/src/optional/python-bindings/tests/test_numeric.py
@@ -0,0 +1,96 @@
+from unittest import TestCase, main
+
+from gnucash import GncNumeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED, \
+ GNC_HOW_RND_NEVER, GNC_HOW_RND_FLOOR, GNC_HOW_RND_CEIL
+
+class TestGncNumeric( TestCase ):
+ def test_defaut(self):
+ num = GncNumeric()
+ self.assertEqual(str(num), "0/1")
+ self.assertEqual(num.num(), 0)
+ self.assertEqual(num.denom(), 1)
+
+ def test_from_num_denom(self):
+ num = GncNumeric(1, 2)
+ self.assertEqual(str(num), "1/2")
+ self.assertEqual(num.num(), 1)
+ self.assertEqual(num.denom(), 2)
+
+ def test_from_int(self):
+ num = GncNumeric(3)
+ self.assertEqual(str(num), "3/1")
+ self.assertEqual(num.num(), 3)
+ self.assertEqual(num.denom(), 1)
+
+ # Safest outcome at current. This can be fixed but correct bounds checks
+ # are required to ensure gint64 is not overflowed.
+ def test_from_long(self):
+ with self.assertRaises(TypeError):
+ GncNumeric(3L)
+
+ def test_from_float(self):
+ num = GncNumeric(3.1, 20, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER)
+ self.assertEqual(str(num), "62/20")
+ self.assertEqual(num.num(), 62)
+ self.assertEqual(num.denom(), 20)
+
+ num = GncNumeric(1/3.0, 10000000000, GNC_HOW_RND_FLOOR)
+ self.assertEqual(str(num), "3333333333/10000000000")
+ self.assertEqual(num.num(), 3333333333)
+ self.assertEqual(num.denom(), 10000000000)
+
+ num = GncNumeric(1/3.0, 10000000000, GNC_HOW_RND_CEIL)
+ self.assertEqual(str(num), "3333333334/10000000000")
+ self.assertEqual(num.num(), 3333333334)
+ self.assertEqual(num.denom(), 10000000000)
+
+ def test_from_float_auto(self):
+ num = GncNumeric(3.1)
+ self.assertEqual(str(num), "31/10")
+ self.assertEqual(num.num(), 31)
+ self.assertEqual(num.denom(), 10)
+
+ def test_from_instance(self):
+ orig = GncNumeric(3)
+ num = GncNumeric(instance=orig.instance)
+ self.assertEqual(str(num), "3/1")
+ self.assertEqual(num.num(), 3)
+ self.assertEqual(num.denom(), 1)
+
+ def test_from_str(self):
+ num = GncNumeric("3.1")
+ self.assertEqual(str(num), "31/10")
+ self.assertEqual(num.num(), 31)
+ self.assertEqual(num.denom(), 10)
+
+ num = GncNumeric("1/3")
+ self.assertEqual(str(num), "1/3")
+ self.assertEqual(num.num(), 1)
+ self.assertEqual(num.denom(), 3)
+
+ def test_to_str(self):
+ num = GncNumeric("1000/3")
+ self.assertEqual(str(num), "1000/3")
+
+ num = GncNumeric(1, 0)
+ self.assertEqual(str(num), "1/0")
+
+ def test_to_double(self):
+ for test_num in [0.0, 1.1, -1.1, 1/3.0]:
+ self.assertEqual(GncNumeric(test_num).to_double(), test_num)
+
+ def test_incorect_args(self):
+ with self.assertRaises(TypeError):
+ GncNumeric(1, 2, 3)
+
+ with self.assertRaises(TypeError):
+ GncNumeric("1", 2)
+
+ with self.assertRaises(TypeError):
+ GncNumeric(1.1, "round")
+
+ with self.assertRaises(TypeError):
+ GncNumeric(complex(1, 1))
+
+if __name__ == '__main__':
+ main()
Summary of changes:
bindings/python/gnucash_core.py | 70 +++++++++++++-----
bindings/python/tests/CMakeLists.txt | 1 +
bindings/python/tests/Makefile.am | 2 +
bindings/python/tests/runTests.py.in | 3 +-
bindings/python/tests/test_account.py | 4 +-
bindings/python/tests/test_numeric.py | 106 +++++++++++++++++++++++++++
common/base-typemaps.i | 41 ++++-------
common/cmake_modules/GncAddSwigCommand.cmake | 23 ++++--
common/test-core/Makefile.am | 2 +-
libgnucash/app-utils/Makefile.am | 2 +-
libgnucash/core-utils/Makefile.am | 2 +-
11 files changed, 201 insertions(+), 55 deletions(-)
create mode 100644 bindings/python/tests/test_numeric.py
More information about the gnucash-changes
mailing list