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