diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py index 5147f7ae38..c688097f4b 100644 --- a/bindings/python/gnucash_core.py +++ b/bindings/python/gnucash_core.py @@ -28,6 +28,8 @@ # @author Jeff Green, ParIT Worker Co-operative # @ingroup python_bindings +import operator + from enum import IntEnum from urllib.parse import urlparse @@ -426,7 +428,7 @@ class GncNumeric(GnuCashCoreClass): elif len(args) == 1: arg = args[0] if isinstance(arg, int): - return gnc_numeric_create(arg ,1) + return gnc_numeric_create(arg, 1) elif isinstance(arg, float): return double_to_gnc_numeric(arg, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER) elif isinstance(arg, str): @@ -434,8 +436,10 @@ class GncNumeric(GnuCashCoreClass): if not string_to_gnc_numeric(arg, instance): raise TypeError('Failed to convert to GncNumeric: ' + str(args)) return instance + elif isinstance(arg, GncNumeric): + return arg.instance else: - raise TypeError('Only single int/float/str allowed: ' + str(args)) + raise TypeError('Only single int/float/str/GncNumeric allowed: ' + str(args)) elif len(args) == 2: if isinstance(args[0], int) and isinstance(args[1], int): return gnc_numeric_create(*args) @@ -451,6 +455,133 @@ class GncNumeric(GnuCashCoreClass): else: raise TypeError('Required single int/float/str or two ints: ' + str(args)) + # from https://docs.python.org/3/library/numbers.html#numbers.Integral + # and https://github.com/python/cpython/blob/3.7/Lib/fractions.py + + def _operator_fallbacks(monomorphic_operator, fallback_operator): + """fallbacks are not needed except for method name, + keep for possible later use""" + def forward(a, b): + if isinstance(b, GncNumeric): + return monomorphic_operator(a, b) + if isinstance(b, (int, float)): + return monomorphic_operator(a, GncNumeric(b)) + else: + return NotImplemented + forward.__name__ = '__' + fallback_operator.__name__ + '__' + forward.__doc__ = monomorphic_operator.__doc__ + + def reverse(b, a): + if isinstance(a, (GncNumeric, int, float)): + return forward(b, a) + else: + return NotImplemented + reverse.__name__ = '__r' + fallback_operator.__name__ + '__' + reverse.__doc__ = monomorphic_operator.__doc__ + + return forward, reverse + + def _add(a, b): + return a.add(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _sub(a, b): + return a.sub(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _mul(a, b): + return a.mul(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _div(a, b): + return a.div(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _floordiv(a, b): + return a.div(b, 1, GNC_HOW_RND_TRUNC) + + __add__, __radd__ = _operator_fallbacks(_add, operator.add) + __iadd__ = __add__ + __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) + __isub__ = __sub__ + __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) + __imul__ = __mul__ + __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) + __itruediv__ = __truediv__ + __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv) + __ifloordiv__ = __floordiv__ + + # Comparisons derived from https://github.com/python/cpython/blob/3.7/Lib/fractions.py + def _lt(a, b): + return a.compare(b) == -1 + + def _gt(a, b): + return a.compare(b) == 1 + + def _le(a, b): + return a.compare(b) in (0,-1) + + def _ge(a, b): + return a.compare(b) in (0,1) + + def _eq(a, b): + return a.compare(b) == 0 + + def _richcmp(self, other, op): + """Helper for comparison operators, for internal use only. + Implement comparison between a GncNumeric instance `self`, + and either another GncNumeric instance, an int or a float + `other`. If `other` is not an instance of that kind, return + NotImplemented. `op` should be one of the six standard + comparison operators. The comparisons are based on + GncNumeric.compare(). + """ + import math + if isinstance(other, GncNumeric): + return op(other) + elif isinstance(other, (int, float)): + return op(GncNumeric(other)) + else: + return NotImplemented + + def __lt__(a, b): + """a < b""" + return a._richcmp(b, a._lt) + + def __gt__(a, b): + """a > b""" + return a._richcmp(b, a._gt) + + def __le__(a, b): + """a <= b""" + return a._richcmp(b, a._le) + + def __ge__(a, b): + """a >= b""" + return a._richcmp(b, a._ge) + + def __eq__(a, b): + """a == b""" + return a._richcmp(b, a._eq) + + def __bool__(a): + """a != 0""" + return bool(a.num()) + + def __float__(self): + return self.to_double() + + def __int__(self): + return int(self.to_double()) + + def __pos__(a): + """+a""" + return GncNumeric(a.num(), a.denom()) + + def __neg__(a): + """-a""" + return a.neg() + + def __abs__(a): + """abs(a)""" + return a.abs() + def to_fraction(self): from fractions import Fraction return Fraction(self.num(), self.denom()) @@ -673,7 +804,7 @@ from gnucash.gnucash_core_c import \ # used for the how argument in arithmetic functions like GncNumeric.add from gnucash.gnucash_core_c import \ GNC_HOW_DENOM_EXACT, GNC_HOW_DENOM_REDUCE, GNC_HOW_DENOM_LCD, \ - GNC_HOW_DENOM_FIXED + GNC_HOW_DENOM_FIXED, GNC_HOW_DENOM_SIGFIG # import account types from gnucash.gnucash_core_c import \ @@ -714,7 +845,8 @@ gncnumeric_dict = { 'add_fixed' : GncNumeric, 'sub_fixed' : GncNumeric, 'convert' : GncNumeric, - 'reduce' : GncNumeric + 'reduce' : GncNumeric, + 'invert' : GncNumeric } methods_return_instance(GncNumeric, gncnumeric_dict)