You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by ke...@apache.org on 2024/11/03 23:50:42 UTC
(tinkerpop) 15/17: update bigdecimal in statics to be consistent with server definition (#2875)
This is an automated email from the ASF dual-hosted git repository.
kenhuuu pushed a commit to branch master-http-final
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit c3e29aad63dc98989b00bec993a0aeb5e24e561e
Author: Yang Xia <55...@users.noreply.github.com>
AuthorDate: Thu Oct 31 17:49:06 2024 -0700
update bigdecimal in statics to be consistent with server definition (#2875)
---
.../python/gremlin_python/process/traversal.py | 6 +---
.../src/main/python/gremlin_python/statics.py | 27 +++++++++++++++--
.../gremlin_python/structure/io/graphsonV4.py | 27 ++++-------------
.../tests/structure/io/test_functionalityio.py | 1 -
.../tests/structure/io/test_graphbinaryv4model.py | 8 ++---
.../python/tests/structure/io/test_graphsonV4.py | 34 ++++------------------
.../tests/structure/io/test_graphsonv4model.py | 14 ++++-----
.../src/main/python/tests/test_statics.py | 18 ++++++++++++
8 files changed, 63 insertions(+), 72 deletions(-)
diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
index 8eac3e8349..bc37dc0506 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py
@@ -841,11 +841,7 @@ class GremlinLang(object):
else:
return f'{arg}D'
if isinstance(arg, BigDecimal):
- # TODO double check implementation when revisiting types definition.
- if arg.scale > 0:
- return f'{arg.unscaled_value:.{arg.scale}f}M'
- else:
- return f'{arg.unscaled_value}M'
+ return f'{arg.value}M'
if isinstance(arg, datetime):
return f'datetime("{arg.isoformat()}")'
diff --git a/gremlin-python/src/main/python/gremlin_python/statics.py b/gremlin-python/src/main/python/gremlin_python/statics.py
index ac05fe2448..b5a83fbc32 100644
--- a/gremlin-python/src/main/python/gremlin_python/statics.py
+++ b/gremlin-python/src/main/python/gremlin_python/statics.py
@@ -16,7 +16,7 @@
# specific language governing permissions and limitations
# under the License.
#
-
+import decimal
from types import FunctionType
from aenum import Enum
@@ -74,12 +74,35 @@ class GremlinType(object):
self.gremlin_type = gremlin_type
-# TODO scale and unscaled value implementations are not following definition, need to revisit
class BigDecimal(object):
+ """
+ Provides a way to represent a BigDecimal for Gremlin.
+ """
def __init__(self, scale, unscaled_value):
self.scale = scale
self.unscaled_value = unscaled_value
+ @property
+ def value(self):
+ self._as_decimal = decimal.Decimal(self.unscaled_value)
+ precision = len(self._as_decimal.as_tuple().digits)
+ with decimal.localcontext(decimal.Context(prec=precision)):
+ return self._as_decimal.scaleb(-self.scale)
+
+"""
+Create a BigDecimal from a number that can be converted to a Decimal. Note precision may be lost during the conversion.
+"""
+def to_bigdecimal(value):
+ try:
+ decimal_value = value if isinstance(value, decimal.Decimal) else decimal.Decimal(str(value))
+ scale = -decimal_value.as_tuple().exponent
+ unscaled_value = int("".join(map(str, decimal_value.as_tuple().digits)))
+ except TypeError:
+ raise ValueError("BigDecimal does not support NaN, Infinity or -Infinity")
+ except Exception as err:
+ raise ValueError(f'Encountered error: {err}. Value must be able to convert to a Decimal.')
+ return BigDecimal(scale, unscaled_value if decimal_value >= 0 else -unscaled_value)
+
staticMethods = {}
staticEnums = {}
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV4.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV4.py
index 7a63c8bee7..d20d3b4e31 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV4.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV4.py
@@ -29,7 +29,7 @@ from aenum import Enum
from isodate import parse_duration, duration_isoformat
from gremlin_python import statics
-from gremlin_python.statics import FloatType, BigDecimal, ShortType, IntType, LongType, TypeType, DictType, ListType, SetType, SingleByte, SingleChar
+from gremlin_python.statics import FloatType, BigDecimal, ShortType, IntType, LongType, TypeType, DictType, ListType, SetType, SingleByte, SingleChar, to_bigdecimal
from gremlin_python.process.traversal import Direction, P, TextP, Traversal, Traverser, TraversalStrategy, T
from gremlin_python.structure.graph import Edge, Property, Vertex, VertexProperty, Path
from gremlin_python.structure.io.util import HashableDict, SymbolUtil
@@ -455,35 +455,18 @@ class FloatIO(_NumberIO):
return cls.python_type(v)
-class BigDecimalIO(_NumberIO):
- python_type = Decimal
+class BigDecimalIO(_GraphSONTypeIO):
+ python_type = BigDecimal
graphson_type = "g:BigDecimal"
graphson_base_type = "BigDecimal"
@classmethod
def dictify(cls, n, writer):
- if isinstance(n, bool): # because isinstance(False, int) and isinstance(True, int)
- return n
- elif math.isnan(n):
- return GraphSONUtil.typed_value(cls.graphson_base_type, "NaN", "g")
- elif math.isinf(n) and n > 0:
- return GraphSONUtil.typed_value(cls.graphson_base_type, "Infinity", "g")
- elif math.isinf(n) and n < 0:
- return GraphSONUtil.typed_value(cls.graphson_base_type, "-Infinity", "g")
- else:
- return GraphSONUtil.typed_value(cls.graphson_base_type, str(n), "g")
+ return GraphSONUtil.typed_value(cls.graphson_base_type, str(n.value), "g")
@classmethod
def objectify(cls, v, _):
- if isinstance(v, str):
- if v == 'NaN':
- return Decimal('nan')
- elif v == "Infinity":
- return Decimal('inf')
- elif v == "-Infinity":
- return Decimal('-inf')
-
- return Decimal(v)
+ return to_bigdecimal(v)
class DoubleIO(FloatIO):
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_functionalityio.py b/gremlin-python/src/main/python/tests/structure/io/test_functionalityio.py
index be74a28710..8dfe37adab 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_functionalityio.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_functionalityio.py
@@ -157,7 +157,6 @@ def test_bigint_negative(remote_connection):
g.V(vid).drop().iterate()
-@pytest.mark.skip(reason="BigDecimal implementation needs revisiting")
def test_bigdecimal(remote_connection):
if not isinstance(remote_connection._client.response_serializer(), GraphBinarySerializersV4):
return
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryv4model.py b/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryv4model.py
index ab18c91443..63298d4bcd 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryv4model.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphbinaryv4model.py
@@ -48,22 +48,20 @@ def read_file_by_name(resource_name):
def test_pos_bigdecimal():
def decimal_cmp(x, y):
- if x.scale == y.scale and x.unscaled_value == y.unscaled_value:
+ if x.scale == y.scale and x.unscaled_value == y.unscaled_value and x.value == y.value:
return True
else:
return False
- # gremlin-python adds an extra 0 byte to the value.
run_writeread("pos-bigdecimal", decimal_cmp)
def test_neg_bigdecimal():
def decimal_cmp(x, y):
- if x.scale == y.scale and x.unscaled_value == y.unscaled_value:
+ if x.scale == y.scale and x.unscaled_value == y.unscaled_value and x.value == y.value:
return True
else:
return False
-
- # gremlin-python adds an extra 0 byte to the value.
+
run_writeread("neg-bigdecimal", decimal_cmp)
def test_pos_biginteger():
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV4.py b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV4.py
index 942fb0f0ea..44b2fa7187 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphsonV4.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphsonV4.py
@@ -152,36 +152,15 @@ class TestGraphSONReader:
"@type": "g:BigDecimal",
"@value": 31.2
}))
- assert isinstance(x, Decimal)
- assert Decimal(31.2) == x
+ assert isinstance(x, BigDecimal)
+ assert Decimal('31.2') == x.value
##
x = self.graphson_reader.read_object(json.dumps({
"@type": "g:BigDecimal",
"@value": 123456789987654321123456789987654321
}))
- assert isinstance(x, Decimal)
- assert Decimal('123456789987654321123456789987654321') == x
- ##
- x = self.graphson_reader.read_object(json.dumps({
- "@type": "g:BigDecimal",
- "@value": "NaN"
- }))
- assert isinstance(x, Decimal)
- assert math.isnan(x)
- ##
- x = self.graphson_reader.read_object(json.dumps({
- "@type": "g:BigDecimal",
- "@value": "Infinity"
- }))
- assert isinstance(x, Decimal)
- assert math.isinf(x) and x > 0
- ##
- x = self.graphson_reader.read_object(json.dumps({
- "@type": "g:BigDecimal",
- "@value": "-Infinity"
- }))
- assert isinstance(x, Decimal)
- assert math.isinf(x) and x < 0
+ assert isinstance(x, BigDecimal)
+ assert to_bigdecimal(123456789987654321123456789987654321).value == x.value
##
x = self.graphson_reader.read_object(json.dumps({
"@type": "g:BigInteger",
@@ -371,10 +350,7 @@ class TestGraphSONWriter:
assert {"@type": "g:Double", "@value": "NaN"} == json.loads(self.graphson_writer.write_object(float('nan')))
assert {"@type": "g:Double", "@value": "Infinity"} == json.loads(self.graphson_writer.write_object(float('inf')))
assert {"@type": "g:Double", "@value": "-Infinity"} == json.loads(self.graphson_writer.write_object(float('-inf')))
- assert {"@type": "g:BigDecimal", "@value": "123456789987654321123456789987654321"} == json.loads(self.graphson_writer.write_object(Decimal('123456789987654321123456789987654321')))
- assert {"@type": "g:BigDecimal", "@value": "NaN"} == json.loads(self.graphson_writer.write_object(Decimal('nan')))
- assert {"@type": "g:BigDecimal", "@value": "Infinity"} == json.loads(self.graphson_writer.write_object(Decimal('inf')))
- assert {"@type": "g:BigDecimal", "@value": "-Infinity"} == json.loads(self.graphson_writer.write_object(Decimal('-inf')))
+ assert {"@type": "g:BigDecimal", "@value": "123.456789987654321123456789987654321"} == json.loads(self.graphson_writer.write_object(to_bigdecimal("123.456789987654321123456789987654321")))
assert {"@type": "g:BigInteger", "@value": "123456789987654321123456789987654321"} == json.loads(self.graphson_writer.write_object(long(123456789987654321123456789987654321)))
assert {"@type": "g:BigInteger", "@value": "123456789987654321123456789987654321"} == json.loads(self.graphson_writer.write_object(123456789987654321123456789987654321))
assert """true""" == self.graphson_writer.write_object(True)
diff --git a/gremlin-python/src/main/python/tests/structure/io/test_graphsonv4model.py b/gremlin-python/src/main/python/tests/structure/io/test_graphsonv4model.py
index 683ff030a5..6f6886e860 100644
--- a/gremlin-python/src/main/python/tests/structure/io/test_graphsonv4model.py
+++ b/gremlin-python/src/main/python/tests/structure/io/test_graphsonv4model.py
@@ -47,27 +47,25 @@ def read_file_by_name(resource_name):
with open(full_name, 'r') as resource_file:
return json.dumps(json.load(resource_file), separators=(',', ':'))
-@pytest.mark.skip(reason="enable after big decimal implementation/serialization is unified in python (")
def test_pos_bigdecimal():
def decimal_cmp(x, y):
- if x.scale == y.scale and x.unscaled_value == y.unscaled_value:
+ # python json library can only read a BigDecimal into float during deser, precision will be lost with GraphSON
+ # so compare float only
+ if float(x.value) == float(y.value):
return True
else:
return False
- # gremlin-python adds an extra 0 byte to the value.
run_writeread("pos-bigdecimal", decimal_cmp)
-@pytest.mark.skip(reason="enable after big decimal implementation/serialization is unified in python")
def test_neg_bigdecimal():
def decimal_cmp(x, y):
- if x.scale == y.scale and x.unscaled_value == y.unscaled_value:
+ if float(x.value) == float(y.value):
return True
else:
return False
-
- # gremlin-python adds an extra 0 byte to the value.
- run_writeread("neg-bigdecimal", decimal_cmp())
+
+ run_writeread("neg-bigdecimal", decimal_cmp)
def test_pos_biginteger():
# gremlin-python adds an extra 0 byte to the value.
diff --git a/gremlin-python/src/main/python/tests/test_statics.py b/gremlin-python/src/main/python/tests/test_statics.py
index 2c3f864754..e22718e8e1 100644
--- a/gremlin-python/src/main/python/tests/test_statics.py
+++ b/gremlin-python/src/main/python/tests/test_statics.py
@@ -19,6 +19,8 @@
__author__ = 'Marko A. Rodriguez (http://markorodriguez.com)'
+from decimal import Decimal
+
from gremlin_python import statics
from gremlin_python.process.traversal import Cardinality
from gremlin_python.process.traversal import P
@@ -63,3 +65,19 @@ class TestStatics(object):
raise Exception("SingleChar should throw a value error if input is not a single character string")
except ValueError:
pass
+
+ def test_bigdecimal(self):
+ assert statics.to_bigdecimal(1.23456).value == statics.BigDecimal(5,123456).value
+ assert statics.to_bigdecimal(-1.23456).value == statics.BigDecimal(5,-123456).value
+ # make sure the precision isn't changed globally
+ assert Decimal("123456789").scaleb(-5) == Decimal('1234.56789')
+ try:
+ statics.to_bigdecimal('NaN')
+ raise Exception("to_bigdecimal should throw a value error with NaN, Infinity or -Infinity")
+ except ValueError:
+ pass
+ try:
+ statics.to_bigdecimal('abc')
+ raise Exception("to_bigdecimal should throw a value error if input is not a convertable to Decimal")
+ except ValueError:
+ pass