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))

Reply via email to