This is an automated email from the ASF dual-hosted git repository.
Cole-Greer pushed a commit to branch 3.7-dev
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/3.7-dev by this push:
new bd141ec03e TINKERPOP-2363 Promote out-of-range python int to Long in
GraphBinary (#3672)
bd141ec03e is described below
commit bd141ec03e77d1c0f9cc1c44617191f94fa5e3a9
Author: KAI <[email protected]>
AuthorDate: Wed Sep 30 02:43:52 2026 +0530
TINKERPOP-2363 Promote out-of-range python int to Long in GraphBinary
(#3672)
gremlin-python's GraphBinary IntIO inherited dictify from LongIO, whose
guard
validates against the Java long range while IntIO packs with int32_pack.
Python 3
has a single arbitrary precision int, so python_type = IntType matches
every plain
int: a value between the int and long bounds passed the guard and then
failed
inside struct.pack with an error naming neither the value nor a remedy. The
same
value serializes fine under GraphSON, which has coerced numerics to Java
ranges
since TINKERPOP-2360, so switching to the default serializer broke working
code.
IntIO now writes a Long when the value falls outside the Java int range.
Values
inside the range are byte-identical to before, statics.long and
statics.bigint
remain explicit overrides, and values beyond the Java long range still
raise the
existing "Value too big, please use bigint Gremlin type" error. Promotion is
skipped for value-only writes, which have no type code to change.
---
CHANGELOG.asciidoc | 2 ++
docs/src/reference/gremlin-variants.asciidoc | 5 +++
.../gremlin_python/structure/io/graphbinaryV1.py | 13 +++++++
.../python/tests/unit/io/test_graphbinaryV1.py | 40 ++++++++++++++++++++++
4 files changed, 60 insertions(+)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index f97495ec38..b4e56b9526 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,8 @@
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
[[release-3-7-8]]
=== TinkerPop 3.7.8 (Release Date: NOT OFFICIALLY RELEASED YET)
+* Fixed `gremlin-python` GraphBinary serialization of `int` values outside the
Java `int` range, which raised a `struct.error` rather than writing a `Long`.
+
[[release-3-7-7]]
=== TinkerPop 3.7.7 (Release Date: September 1, 2026)
diff --git a/docs/src/reference/gremlin-variants.asciidoc
b/docs/src/reference/gremlin-variants.asciidoc
index d15c9291b0..79e7b575be 100644
--- a/docs/src/reference/gremlin-variants.asciidoc
+++ b/docs/src/reference/gremlin-variants.asciidoc
@@ -2629,6 +2629,11 @@ from gremlin_python.statics import SingleChar # Java
char type
from gremlin_python.statics import GremlinType # Java Class
----
+Python has a single arbitrary precision `int`, so the serializer picks the
Java type from the value:
+an `int` that fits the Java `int` range is sent as an `int` and anything wider
is sent as a `long`.
+Values beyond the Java `long` range have to be wrapped in `bigint`. Use `long`
when a graph needs the
+wider type for a value that would otherwise fit in an `int`.
+
anchor:python-configuration[]
[[gremlin-python-configuration]]
=== Configuration
diff --git
a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
index e82d0ed41c..dff4ceb535 100644
---
a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
+++
b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
@@ -276,6 +276,19 @@ class IntIO(LongIO):
byte_format_pack = int32_pack
byte_format_unpack = int32_unpack
+ @classmethod
+ def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
+ # Python has one arbitrary precision int type, so every plain int
arrives here
+ # regardless of magnitude. Anything outside the Java int range is
written as a Long
+ # instead of reaching int32_pack, which fails with a bare
struct.error. GraphSON
+ # coerces the same way (TINKERPOP-2360), and statics.long and
statics.bigint stay
+ # available as explicit overrides. Promotion needs a type code in
front of the
+ # value, so a value-only write keeps the fixed int32 layout.
+ if not as_value and not (-2147483648 <= obj <= 2147483647):
+ return LongIO.dictify(obj, writer, to_extend, as_value, nullable)
+
+ return super().dictify(obj, writer, to_extend, as_value, nullable)
+
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: cls.read_int(b),
nullable)
diff --git a/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py
b/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py
index b4624584b4..19d34f5577 100644
--- a/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py
+++ b/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py
@@ -21,6 +21,8 @@ import datetime
import uuid
import math
+import pytest
+
from gremlin_python.statics import timestamp, long, bigint, BigDecimal,
SingleByte, SingleChar, ByteBufferType
from gremlin_python.structure.graph import Vertex, Edge, Property,
VertexProperty, Path
from gremlin_python.structure.io.graphbinaryV1 import GraphBinaryWriter,
GraphBinaryReader
@@ -44,6 +46,44 @@ class TestGraphSONWriter(object):
output =
self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
assert x == output
+ def test_int_out_of_int32_range_promotes_to_long(self):
+ # Python has one arbitrary precision int type, so a plain int beyond
the Java int
+ # range used to reach int32_pack and fail with a bare struct.error
(TINKERPOP-2363).
+ for x in [2 ** 31, -2 ** 31 - 1, 3000000000, 2 ** 63 - 1, -2 ** 63]:
+ output =
self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
+ assert x == output
+
+ def test_int_wire_bytes_at_int32_boundary(self):
+ # The type code stays Int (0x01) inside the Java int range and becomes
Long (0x02)
+ # immediately outside it, so promotion never widens a value that
already fits.
+ cases = {
+ 2 ** 31 - 1: b'\x01\x00\x7f\xff\xff\xff',
+ -2 ** 31: b'\x01\x00\x80\x00\x00\x00',
+ 2 ** 31: b'\x02\x00\x00\x00\x00\x00\x80\x00\x00\x00',
+ -2 ** 31 - 1: b'\x02\x00\xff\xff\xff\xff\x7f\xff\xff\xff',
+ }
+ for value, expected in cases.items():
+ assert bytes(self.graphbinary_writer.write_object(value)) ==
expected
+
+ def test_int_out_of_int64_range_still_raises(self):
+ # Promotion stops at Long. bigint stays an explicit choice.
+ for x in [2 ** 63, -2 ** 63 - 1]:
+ with pytest.raises(Exception, match='Value too big'):
+ self.graphbinary_writer.write_object(x)
+
+ def test_int_out_of_int32_range_nested(self):
+ # The reported failure arrived as server-assigned ids inside nested
bindings.
+ for x in [[[3000000000, 5000000000]], {'id': 3000000000}]:
+ output =
self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
+ assert x == output
+
+ def test_bool_is_not_affected_by_int_promotion(self):
+ # bool subclasses int, so it must keep resolving to the Boolean
serializer.
+ for x in [True, False]:
+ output =
self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))
+ assert x == output
+ assert isinstance(output, bool)
+
def test_long(self):
x = long(100)
output =
self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(x))