Skip to content

Commit 0ac0710

Browse files
authored
Fixed statics.BigDecimal in gremlin-python to properly calculate scale and unscaled_value, and added value attribute to return a decimal.Decimal representation. Updated GraphSON in gremlin-python to return statics.BigDecimal instead of decimal.Decimal for consistency with GraphBinary. (#3256)
1 parent 074d634 commit 0ac0710

File tree

13 files changed

+104
-119
lines changed

13 files changed

+104
-119
lines changed

CHANGELOG.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>.
5151
* Added missing strategies to `strategies.py` in `gremlin-python`.
5252
* Fixed fully qualified class names for `TraversalStrategy` names in `gremlin-dotnet`.
5353
* Updated `OptionsStrategy` in `gremlin-python` to take options directly as keyword arguments.
54+
* Fixed `statics.BigDecimal` implementation in `gremlin-python` to properly calculate `scale` and `unscaled_value`, and added `value` attribute to return a `decimal.Decimal` representation.
55+
* Updated `GraphSON` in `gremlin-python` to return `statics.BigDecimal` instead of `decimal.Decimal` for consistency with `GraphBinary`.
5456
* Added static `instance()` method to `ElementIdStrategy` to an instance with the default configuration.
5557
* Updated `ElementIdStrategy.getConfiguration()` to help with serialization.
5658
* Added grammar-based `Translator` for all languages including explicit ones for Java and anonymization.

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/PythonTranslateVisitor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,15 @@ public Void visitFloatLiteral(final GremlinParser.FloatLiteralContext ctx) {
246246
final int lastCharIndex = floatLiteral.length() - 1;
247247
final char lastChar = floatLiteral.charAt(lastCharIndex);
248248
switch (lastChar) {
249-
case 'm':
250249
case 'f':
251250
case 'd':
252251
sb.append(floatLiteral, 0, lastCharIndex);
253252
break;
253+
case 'm':
254+
sb.append("bigdecimal(");
255+
sb.append(floatLiteral, 0, lastCharIndex);
256+
sb.append(")");
257+
break;
254258
default:
255259
// everything else just goes as specified
256260
sb.append(floatLiteral);

gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/translator/GremlinTranslatorTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public static Collection<Object[]> data() {
331331
"g.with('x', 1.0g)",
332332
"g.with(\"x\", new BigDecimal(\"1.0\"))",
333333
"g.with_(\"x\", 1.0)",
334-
"g.with_('x', 1.0)"},
334+
"g.with_('x', bigdecimal(1.0))"},
335335
{"g.with('x', -1.0m)",
336336
null,
337337
"g.with(string0, bigdecimal0)",
@@ -340,7 +340,7 @@ public static Collection<Object[]> data() {
340340
"g.with('x', -1.0g)",
341341
"g.with(\"x\", new BigDecimal(\"-1.0\"))",
342342
"g.with_(\"x\", -1.0)",
343-
"g.with_('x', -1.0)"},
343+
"g.with_('x', bigdecimal(-1.0))"},
344344
{"g.with('x', -1.0M)",
345345
"g.with('x', -1.0m)",
346346
"g.with(string0, bigdecimal0)",
@@ -349,7 +349,7 @@ public static Collection<Object[]> data() {
349349
"g.with('x', -1.0g)",
350350
"g.with(\"x\", new BigDecimal(\"-1.0\"))",
351351
"g.with_(\"x\", -1.0)",
352-
"g.with_('x', -1.0)"},
352+
"g.with_('x', bigdecimal(-1.0))"},
353353
{"g.with('x', 1b)",
354354
null,
355355
"g.with(string0, byte0)",

gremlin-python/build/generate.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer ->
5757
'from radish import world\n' +
5858
'import datetime\n' +
5959
'import uuid\n' +
60-
'from gremlin_python.statics import long, bigint, GremlinType\n' +
60+
'from gremlin_python.statics import long, bigint, bigdecimal, GremlinType\n' +
6161
'from gremlin_python.process.anonymous_traversal import traversal\n' +
6262
'from gremlin_python.process.strategies import *\n' +
6363
'from gremlin_python.process.traversal import TraversalStrategy\n' +

gremlin-python/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ services:
7171
&& klist
7272
&& pip install .[test,kerberos]
7373
&& pytest
74-
&& radish -f dots -e -t -b ./radish ./gremlin-test --tags='not DataBigDecimal' --user-data='serializer=application/vnd.gremlin-v3.0+json'
75-
&& radish -f dots -e -t -b ./radish ./gremlin-test --tags='not DataBigDecimal' --user-data='serializer=application/vnd.graphbinary-v1.0';
74+
&& radish -f dots -e -t -b ./radish ./gremlin-test --user-data='serializer=application/vnd.gremlin-v3.0+json'
75+
&& radish -f dots -e -t -b ./radish ./gremlin-test --user-data='serializer=application/vnd.graphbinary-v1.0';
7676
EXIT_CODE=$$?; chown -R `stat -c "%u:%g" .` .; exit $$EXIT_CODE"
7777
depends_on:
7878
gremlin-server-test-python:

gremlin-python/src/main/python/gremlin_python/statics.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# specific language governing permissions and limitations
1717
# under the License.
1818
#
19-
19+
import decimal
2020
from types import FunctionType
2121
from aenum import Enum
2222

@@ -84,10 +84,48 @@ def __init__(self, gremlin_type):
8484

8585

8686
class BigDecimal(object):
87+
"""
88+
Provides a way to represent a BigDecimal for Gremlin.
89+
"""
8790
def __init__(self, scale, unscaled_value):
8891
self.scale = scale
8992
self.unscaled_value = unscaled_value
9093

94+
@property
95+
def value(self):
96+
self._as_decimal = decimal.Decimal(self.unscaled_value)
97+
precision = len(self._as_decimal.as_tuple().digits)
98+
with decimal.localcontext(decimal.Context(prec=precision)):
99+
return self._as_decimal.scaleb(-self.scale)
100+
101+
def __eq__(self, other):
102+
if not isinstance(other, BigDecimal):
103+
return False
104+
return self.scale == other.scale and self.unscaled_value == other.unscaled_value
105+
106+
def __hash__(self):
107+
return hash((self.scale, self.unscaled_value))
108+
109+
def __repr__(self):
110+
return f"BigDecimal(scale={self.scale}, unscaled_value={self.unscaled_value})"
111+
112+
def __str__(self):
113+
return str(self.value)
114+
115+
"""
116+
Create a BigDecimal from a number that can be converted to a Decimal. Note precision may be lost during the conversion.
117+
"""
118+
def bigdecimal(value):
119+
try:
120+
decimal_value = value if isinstance(value, decimal.Decimal) else decimal.Decimal(str(value))
121+
scale = -decimal_value.as_tuple().exponent
122+
unscaled_value = int("".join(map(str, decimal_value.as_tuple().digits)))
123+
except TypeError:
124+
raise ValueError("BigDecimal does not support NaN, Infinity or -Infinity")
125+
except Exception as err:
126+
raise ValueError(f'Encountered error: {err}. Value must be able to convert to a Decimal.')
127+
return BigDecimal(scale, unscaled_value if decimal_value >= 0 else -unscaled_value)
128+
91129

92130
staticMethods = {}
93131
staticEnums = {}

gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV2d0.py

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
from isodate import parse_duration, duration_isoformat
3131

3232
from gremlin_python import statics
33-
from gremlin_python.statics import FloatType, FunctionType, ShortType, IntType, LongType, TypeType, SingleByte, ByteBufferType, SingleChar
33+
from gremlin_python.statics import FloatType, FunctionType, ShortType, IntType, LongType, TypeType, SingleByte, \
34+
ByteBufferType, SingleChar, BigDecimal, bigdecimal
3435
from gremlin_python.process.traversal import Binding, Bytecode, P, TextP, Traversal, Traverser, TraversalStrategy
3536
from gremlin_python.structure.graph import Edge, Property, Vertex, VertexProperty, Path
3637
from gremlin_python.structure.io.util import SymbolUtil
@@ -447,34 +448,18 @@ def objectify(cls, v, _):
447448

448449

449450
class BigDecimalIO(_NumberIO):
450-
python_type = Decimal
451+
python_type = BigDecimal
451452
graphson_type = "gx:BigDecimal"
452453
graphson_base_type = "BigDecimal"
453454

454455
@classmethod
455456
def dictify(cls, n, writer):
456-
if isinstance(n, bool): # because isinstance(False, int) and isinstance(True, int)
457-
return n
458-
elif math.isnan(n):
459-
return GraphSONUtil.typed_value(cls.graphson_base_type, "NaN", "gx")
460-
elif math.isinf(n) and n > 0:
461-
return GraphSONUtil.typed_value(cls.graphson_base_type, "Infinity", "gx")
462-
elif math.isinf(n) and n < 0:
463-
return GraphSONUtil.typed_value(cls.graphson_base_type, "-Infinity", "gx")
464-
else:
465-
return GraphSONUtil.typed_value(cls.graphson_base_type, str(n), "gx")
457+
print(n)
458+
return GraphSONUtil.typed_value(cls.graphson_base_type, str(n.value), "gx")
466459

467460
@classmethod
468461
def objectify(cls, v, _):
469-
if isinstance(v, str):
470-
if v == 'NaN':
471-
return Decimal('nan')
472-
elif v == "Infinity":
473-
return Decimal('inf')
474-
elif v == "-Infinity":
475-
return Decimal('-inf')
476-
477-
return Decimal(v)
462+
return bigdecimal(v)
478463

479464

480465
class DoubleIO(FloatIO):

gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV3d0.py

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
from isodate import parse_duration, duration_isoformat
3030

3131
from gremlin_python import statics
32-
from gremlin_python.statics import FloatType, FunctionType, ShortType, IntType, LongType, TypeType, DictType, ListType, SetType, SingleByte, ByteBufferType, SingleChar
32+
from gremlin_python.statics import FloatType, FunctionType, ShortType, IntType, LongType, TypeType, DictType, ListType, \
33+
SetType, SingleByte, ByteBufferType, SingleChar, BigDecimal, bigdecimal
3334
from gremlin_python.process.traversal import Binding, Bytecode, Direction, P, TextP, Traversal, Traverser, TraversalStrategy, T
3435
from gremlin_python.structure.graph import Edge, Property, Vertex, VertexProperty, Path
3536
from gremlin_python.structure.io.util import HashableDict, SymbolUtil
@@ -544,34 +545,17 @@ def objectify(cls, v, _):
544545

545546

546547
class BigDecimalIO(_NumberIO):
547-
python_type = Decimal
548+
python_type = BigDecimal
548549
graphson_type = "gx:BigDecimal"
549550
graphson_base_type = "BigDecimal"
550551

551552
@classmethod
552553
def dictify(cls, n, writer):
553-
if isinstance(n, bool): # because isinstance(False, int) and isinstance(True, int)
554-
return n
555-
elif math.isnan(n):
556-
return GraphSONUtil.typed_value(cls.graphson_base_type, "NaN", "gx")
557-
elif math.isinf(n) and n > 0:
558-
return GraphSONUtil.typed_value(cls.graphson_base_type, "Infinity", "gx")
559-
elif math.isinf(n) and n < 0:
560-
return GraphSONUtil.typed_value(cls.graphson_base_type, "-Infinity", "gx")
561-
else:
562-
return GraphSONUtil.typed_value(cls.graphson_base_type, str(n), "gx")
554+
return GraphSONUtil.typed_value(cls.graphson_base_type, str(n.value), "gx")
563555

564556
@classmethod
565557
def objectify(cls, v, _):
566-
if isinstance(v, str):
567-
if v == 'NaN':
568-
return Decimal('nan')
569-
elif v == "Infinity":
570-
return Decimal('inf')
571-
elif v == "-Infinity":
572-
return Decimal('-inf')
573-
574-
return Decimal(v)
558+
return bigdecimal(v)
575559

576560

577561
class DoubleIO(FloatIO):

gremlin-python/src/main/python/radish/feature_steps.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import json
2222
import re
2323
import uuid
24-
from gremlin_python.statics import long
24+
from gremlin_python.statics import long, bigdecimal
2525
from gremlin_python.structure.graph import Path, Vertex
2626
from gremlin_python.process.anonymous_traversal import traversal
2727
from gremlin_python.process.graph_traversal import __
@@ -260,8 +260,10 @@ def _convert(val, ctx):
260260
return float("inf")
261261
elif isinstance(val, str) and re.match(r"^d\[-Infinity\]$", val): # parse -inf
262262
return float("-inf")
263-
elif isinstance(val, str) and re.match(r"^d\[.*\]\.[bsilfdmn]$", val): # parse numeric
263+
elif isinstance(val, str) and re.match(r"^d\[.*\]\.[bsilfdn]$", val): # parse numeric
264264
return float(val[2:-3]) if val[2:-3].__contains__(".") else long(val[2:-3])
265+
elif isinstance(val, str) and re.match(r"^d\[.*\]\.m$", val): # parse bigDecimal
266+
return bigdecimal(val[2:-3])
265267
elif isinstance(val, str) and re.match(r"^v\[.*\]\.id$", val): # parse vertex id
266268
return __find_cached_element(ctx, graph_name, val[2:-4], "v").id
267269
elif isinstance(val, str) and re.match(r"^v\[.*\]\.sid$", val): # parse vertex id as string

gremlin-python/src/main/python/radish/gremlin.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from radish import world
2828
import datetime
2929
import uuid
30-
from gremlin_python.statics import long, bigint, GremlinType
30+
from gremlin_python.statics import long, bigint, bigdecimal, GremlinType
3131
from gremlin_python.process.anonymous_traversal import traversal
3232
from gremlin_python.process.strategies import *
3333
from gremlin_python.process.traversal import TraversalStrategy
@@ -1827,14 +1827,14 @@
18271827
'InjectXInfX_ltXNegInfX': [(lambda g:g.inject(float('inf')).is_(P.lt(float('-inf'))))],
18281828
'InjectXNegInfX_ltXInfX': [(lambda g:g.inject(float('-inf')).is_(P.lt(float('inf'))))],
18291829
'InjectXNegInfX_gtXInfX': [(lambda g:g.inject(float('-inf')).is_(P.gt(float('inf'))))],
1830-
'Primitives_Number_eqXbyteX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1831-
'Primitives_Number_eqXshortX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1832-
'Primitives_Number_eqXintX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1833-
'Primitives_Number_eqXlongX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1834-
'Primitives_Number_eqXbigintX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1835-
'Primitives_Number_eqXfloatX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1836-
'Primitives_Number_eqXdoubleX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1837-
'Primitives_Number_eqXbigdecimalX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, 1, bigint(1)]).unfold().where(__.is_(xx1)))],
1830+
'Primitives_Number_eqXbyteX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
1831+
'Primitives_Number_eqXshortX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
1832+
'Primitives_Number_eqXintX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
1833+
'Primitives_Number_eqXlongX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
1834+
'Primitives_Number_eqXbigintX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
1835+
'Primitives_Number_eqXfloatX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
1836+
'Primitives_Number_eqXdoubleX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
1837+
'Primitives_Number_eqXbigdecimalX': [(lambda g, xx1=None:g.inject([1, 1, 1, long(1), 1, 1, 1000, bigdecimal(1), bigint(1)]).unfold().where(__.is_(xx1)))],
18381838
'g_V_values_order': [(lambda g:g.V().values().order())],
18391839
'g_V_properties_order': [(lambda g:g.V().properties().order())],
18401840
'g_V_properties_order_id': [(lambda g:g.V().properties().order().id_())],

0 commit comments

Comments
 (0)