You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2025/07/10 04:05:09 UTC

(fory) branch main updated: feat(python): Add StatefulSerializer using __getstate__, __setstate__ (#2400)

This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 44ca29c70 feat(python): Add StatefulSerializer using __getstate__, __setstate__ (#2400)
44ca29c70 is described below

commit 44ca29c70cba3a80730dc89e9bbadd2696c2768f
Author: Emre Şafak <39...@users.noreply.github.com>
AuthorDate: Thu Jul 10 00:05:04 2025 -0400

    feat(python): Add StatefulSerializer using __getstate__, __setstate__ (#2400)
    
    ## What does this PR do?
    
    * add support for objects with `__getstate__` and `__setstate__` in the
    registry
    * implement StatefulSerializer for cross-language, cross-version
    * use StatefulSerializer for objects with `__getstate__`/`__setstate__`
    (excluding pandas)
    * update tests to include various stateful and argument-based objects
    * test serialization/deserialization and serializer selection
    correctness
    * ensure nested and reference tracking scenarios are covered
    * validate that objects with `__getnewargs_ex__` and `__getnewargs__`
    work properly
    * verify that the new serializer is used and state is restored
    accurately
    
    ## Related issues
    
    Closes #2293
    
    ## Does this PR introduce any user-facing change?
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    Not done
    
    ---------
    
    Co-authored-by: Emre Şafak <es...@users.noreply.github.com>
---
 python/pyfory/_registry.py                        |  12 +
 python/pyfory/serializer.py                       |  46 ++++
 python/pyfory/tests/test_stateful_reproduction.py | 152 +++++++++++
 python/pyfory/tests/test_stateful_serializer.py   | 307 ++++++++++++++++++++++
 python/pyfory/tests/test_struct.py                |  30 +--
 5 files changed, 528 insertions(+), 19 deletions(-)

diff --git a/python/pyfory/_registry.py b/python/pyfory/_registry.py
index dd999ce2c..3a4cd131b 100644
--- a/python/pyfory/_registry.py
+++ b/python/pyfory/_registry.py
@@ -59,6 +59,7 @@ from pyfory.serializer import (
     PickleStrongCacheSerializer,
     PickleSerializer,
     DataClassSerializer,
+    StatefulSerializer,
 )
 from pyfory.meta.metastring import MetaStringEncoder, MetaStringDecoder
 from pyfory.type import (
@@ -459,6 +460,8 @@ class TypeResolver:
                 type_id = TypeId.NAMED_ENUM
             elif type(serializer) is PickleSerializer:
                 type_id = PickleSerializer.PICKLE_TYPE_ID
+            elif isinstance(serializer, StatefulSerializer):
+                type_id = TypeId.NAMED_EXT
             if not self.require_registration:
                 if isinstance(serializer, DataClassSerializer):
                     type_id = TypeId.NAMED_STRUCT
@@ -485,6 +488,15 @@ class TypeResolver:
                 serializer = DataClassSerializer(self.fory, cls)
             elif issubclass(cls, enum.Enum):
                 serializer = EnumSerializer(self.fory, cls)
+            elif hasattr(cls, "__getstate__") and hasattr(cls, "__setstate__"):
+                # Use StatefulSerializer for objects that support __getstate__ and __setstate__
+                # But exclude certain types that have incompatible state methods
+                module_name = getattr(cls, "__module__", "")
+                if module_name.startswith("pandas."):
+                    # Pandas objects have __getstate__/__setstate__ but use incompatible pickle formats
+                    serializer = PickleSerializer(self.fory, cls)
+                else:
+                    serializer = StatefulSerializer(self.fory, cls)
             else:
                 serializer = PickleSerializer(self.fory, cls)
         return serializer
diff --git a/python/pyfory/serializer.py b/python/pyfory/serializer.py
index 4a24829b8..a0e8ebaca 100644
--- a/python/pyfory/serializer.py
+++ b/python/pyfory/serializer.py
@@ -703,6 +703,52 @@ class BytesBufferObject(BufferObject):
         return Buffer(self.binary)
 
 
+class StatefulSerializer(CrossLanguageCompatibleSerializer):
+    """
+    Serializer for objects that support __getstate__ and __setstate__.
+    Uses Fory's native serialization for better cross-language support.
+    """
+
+    def __init__(self, fory, cls):
+        super().__init__(fory, cls)
+        self.cls = cls
+        # Cache the method references as fields in the serializer.
+        self._getnewargs_ex = getattr(cls, "__getnewargs_ex__", None)
+        self._getnewargs = getattr(cls, "__getnewargs__", None)
+
+    def write(self, buffer, value):
+        state = value.__getstate__()
+        args = ()
+        kwargs = {}
+        if self._getnewargs_ex is not None:
+            args, kwargs = self._getnewargs_ex(value)
+        elif self._getnewargs is not None:
+            args = self._getnewargs(value)
+
+        # Serialize constructor arguments first
+        self.fory.serialize_ref(buffer, args)
+        self.fory.serialize_ref(buffer, kwargs)
+
+        # Then serialize the state
+        self.fory.serialize_ref(buffer, state)
+
+    def read(self, buffer):
+        args = self.fory.deserialize_ref(buffer)
+        kwargs = self.fory.deserialize_ref(buffer)
+        state = self.fory.deserialize_ref(buffer)
+
+        if args or kwargs:
+            # Case 1: __getnewargs__ was used. Re-create by calling __init__.
+            obj = self.cls(*args, **kwargs)
+        else:
+            # Case 2: Only __getstate__ was used. Create without calling __init__.
+            obj = self.cls.__new__(self.cls)
+
+        if state:
+            obj.__setstate__(state)
+        return obj
+
+
 class PickleSerializer(Serializer):
     PICKLE_TYPE_ID = 96
 
diff --git a/python/pyfory/tests/test_stateful_reproduction.py b/python/pyfory/tests/test_stateful_reproduction.py
new file mode 100644
index 000000000..55628e5ca
--- /dev/null
+++ b/python/pyfory/tests/test_stateful_reproduction.py
@@ -0,0 +1,152 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from pyfory import Fory, Language
+
+
+# Test class with __getstate__ and __setstate__
+class StatefulObject:
+    def __init__(self, value, secret=None):
+        self.value = value
+        self.secret = secret or "default_secret"
+        self.computed = self.value * 2
+
+    def __getstate__(self):
+        # Only serialize value, not secret or computed
+        return {"value": self.value}
+
+    def __setstate__(self, state):
+        self.value = state["value"]
+        self.secret = "restored_secret"
+        self.computed = self.value * 2
+
+    def __eq__(self, other):
+        return isinstance(other, StatefulObject) and self.value == other.value and self.computed == other.computed
+        # Note: secret is expected to be different after deserialization
+
+    def __repr__(self):
+        return f"StatefulObject(value={self.value}, secret={self.secret}, computed={self.computed})"
+
+
+# Test class with getnewargs_ex
+class ImmutableWithArgs:
+    def __init__(self, x, y, name="default"):
+        self._x = x
+        self._y = y
+        self._name = name
+
+    def __getnewargs_ex__(self):
+        return (self._x, self._y), {"name": self._name}
+
+    def __getstate__(self):
+        return {"extra_data": "some_extra"}
+
+    def __setstate__(self, state):
+        self._extra = state.get("extra_data", "none")
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, ImmutableWithArgs)
+            and self._x == other._x
+            and self._y == other._y
+            and self._name == other._name
+            and getattr(self, "_extra", None) == getattr(other, "_extra", None)
+        )
+
+    def __repr__(self):
+        return f"ImmutableWithArgs(x={self._x}, y={self._y}, name={self._name}, extra={getattr(self, '_extra', None)})"
+
+
+# Test class with getnewargs (older style)
+class ImmutableOldStyle:
+    def __init__(self, a, b):
+        self._a = a
+        self._b = b
+
+    def __getnewargs__(self):
+        return self._a, self._b
+
+    def __getstate__(self):
+        return {"metadata": "old_style"}
+
+    def __setstate__(self, state):
+        self._metadata = state.get("metadata", "none")
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, ImmutableOldStyle)
+            and self._a == other._a
+            and self._b == other._b
+            and getattr(self, "_metadata", None) == getattr(other, "_metadata", None)
+        )
+
+    def __repr__(self):
+        return f"ImmutableOldStyle(a={self._a}, b={self._b}, metadata={getattr(self, '_metadata', None)})"
+
+
+def test_current_behavior():
+    print("Testing current behavior with stateful objects...")
+
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    # Test basic stateful object
+    obj1 = StatefulObject(42, "original_secret")
+    print(f"Original: {obj1}")
+
+    serialized = fory.serialize(obj1)
+    deserialized = fory.deserialize(serialized)
+    print(f"Deserialized: {deserialized}")
+    print(f"Equal: {obj1 == deserialized}")
+    print()
+
+    # Test with getnewargs_ex
+    obj2 = ImmutableWithArgs(10, 20, "test")
+    print(f"Original: {obj2}")
+
+    serialized2 = fory.serialize(obj2)
+    deserialized2 = fory.deserialize(serialized2)
+    print(f"Deserialized attributes: {dir(deserialized2)}")
+    print(f"Deserialized vars: {vars(deserialized2)}")
+    try:
+        print(f"Deserialized: {deserialized2}")
+        print(f"Equal: {obj2 == deserialized2}")
+    except Exception as e:
+        print(f"Error in repr/comparison: {e}")
+    print()
+
+    # Test with getnewargs (old style)
+    obj3 = ImmutableOldStyle(100, 200)
+    print(f"Original: {obj3}")
+
+    serialized3 = fory.serialize(obj3)
+    deserialized3 = fory.deserialize(serialized3)
+    print(f"Deserialized: {deserialized3}")
+    print(f"Equal: {obj3 == deserialized3}")
+    print()
+
+    # Check what serializer is being used
+    serializer1 = fory.type_resolver.get_serializer(StatefulObject)
+    serializer2 = fory.type_resolver.get_serializer(ImmutableWithArgs)
+    serializer3 = fory.type_resolver.get_serializer(ImmutableOldStyle)
+
+    print(f"StatefulObject serializer: {type(serializer1)}")
+    print(f"ImmutableWithArgs serializer: {type(serializer2)}")
+    print(f"ImmutableOldStyle serializer: {type(serializer3)}")
+
+
+if __name__ == "__main__":
+    test_current_behavior()
diff --git a/python/pyfory/tests/test_stateful_serializer.py b/python/pyfory/tests/test_stateful_serializer.py
new file mode 100644
index 000000000..a91c49e38
--- /dev/null
+++ b/python/pyfory/tests/test_stateful_serializer.py
@@ -0,0 +1,307 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from pyfory import Fory, Language
+from pyfory.serializer import StatefulSerializer
+
+
+class BasicStatefulObject:
+    """Basic object with __getstate__ and __setstate__"""
+
+    def __init__(self, value, secret=None):
+        self.value = value
+        self.secret = secret or "default_secret"
+        self.computed = self.value * 2
+
+    def __getstate__(self):
+        # Only serialize value, not secret or computed
+        return {"value": self.value}
+
+    def __setstate__(self, state):
+        self.value = state["value"]
+        self.secret = "restored_secret"
+        self.computed = self.value * 2
+
+    def __eq__(self, other):
+        return isinstance(other, BasicStatefulObject) and self.value == other.value and self.computed == other.computed
+        # Note: secret is expected to be different after deserialization
+
+
+class ImmutableWithArgsEx:
+    """Object with __getnewargs_ex__ and state methods"""
+
+    def __init__(self, x, y, name="default"):
+        self._x = x
+        self._y = y
+        self._name = name
+
+    def __getnewargs_ex__(self):
+        return (self._x, self._y), {"name": self._name}
+
+    def __getstate__(self):
+        return {"extra_data": "some_extra"}
+
+    def __setstate__(self, state):
+        self._extra = state.get("extra_data", "none")
+
+    def __eq__(self, other):
+        if not isinstance(other, ImmutableWithArgsEx):
+            return False
+        return (
+            self._x == other._x
+            and self._y == other._y
+            and self._name == other._name
+            and getattr(self, "_extra", None) == getattr(other, "_extra", None)
+        )
+
+
+class ImmutableWithArgs:
+    """Object with __getnewargs__ (old style) and state methods"""
+
+    def __init__(self, a, b):
+        self._a = a
+        self._b = b
+
+    def __getnewargs__(self):
+        return self._a, self._b
+
+    def __getstate__(self):
+        return {"metadata": "old_style"}
+
+    def __setstate__(self, state):
+        self._metadata = state.get("metadata", "none")
+
+    def __eq__(self, other):
+        if not isinstance(other, ImmutableWithArgs):
+            return False
+        return self._a == other._a and self._b == other._b and getattr(self, "_metadata", None) == getattr(other, "_metadata", None)
+
+
+class StatefulOnlyObject:
+    """Object with only __getstate__ and __setstate__, no constructor args"""
+
+    def __init__(self, data):
+        self.data = data
+        self.processed = f"processed_{data}"
+
+    def __getstate__(self):
+        return {"data": self.data}
+
+    def __setstate__(self, state):
+        self.data = state["data"]
+        self.processed = f"restored_{self.data}"
+
+    def __eq__(self, other):
+        if not isinstance(other, StatefulOnlyObject):
+            return False
+        return self.data == other.data and self.processed == other.processed
+
+
+class ComplexStateObject:
+    """Object with a complex state including nested objects"""
+
+    def __init__(self, name, items=None):
+        self.name = name
+        self.items = items or []
+        self.count = len(self.items)
+
+    def __getstate__(self):
+        return {"name": self.name, "items": self.items, "extra_info": {"serialized_at": "test_time"}}
+
+    def __setstate__(self, state):
+        self.name = state["name"]
+        self.items = state["items"]
+        self.count = len(self.items)
+        self.extra_info = state.get("extra_info", {})
+
+    def __eq__(self, other):
+        if not isinstance(other, ComplexStateObject):
+            return False
+        return (
+            self.name == other.name
+            and self.items == other.items
+            and self.count == other.count
+            and getattr(self, "extra_info", {}) == getattr(other, "extra_info", {})
+        )
+
+
+def test_basic_stateful_object():
+    """Test basic object with __getstate__ and __setstate__"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    obj = BasicStatefulObject(42, "original_secret")
+    serialized = fory.serialize(obj)
+    deserialized = fory.deserialize(serialized)
+
+    # Check that StatefulSerializer is being used
+    serializer = fory.type_resolver.get_serializer(BasicStatefulObject)
+    assert isinstance(serializer, StatefulSerializer)
+
+    # Check deserialization correctness
+    assert deserialized.value == 42
+    assert deserialized.secret == "restored_secret"  # Changed by __setstate__
+    assert deserialized.computed == 84
+    assert obj == deserialized
+
+
+def test_immutable_with_getnewargs_ex():
+    """Test object with __getnewargs_ex__"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    obj = ImmutableWithArgsEx(10, 20, "test")
+    # Simulate the state that would be set by __setstate__ for comparison
+    obj._extra = "some_extra"
+
+    serialized = fory.serialize(obj)
+    deserialized = fory.deserialize(serialized)
+
+    # Check that StatefulSerializer is being used
+    serializer = fory.type_resolver.get_serializer(ImmutableWithArgsEx)
+    assert isinstance(serializer, StatefulSerializer)
+
+    # Check that constructor arguments were used correctly
+    assert deserialized._x == 10
+    assert deserialized._y == 20
+    assert deserialized._name == "test"
+    assert deserialized._extra == "some_extra"  # Set by __setstate__
+    assert obj == deserialized
+
+
+def test_immutable_with_getnewargs():
+    """Test object with __getnewargs__ (old style)"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    obj = ImmutableWithArgs(100, 200)
+    # Simulate the state that would be set by __setstate__ for comparison
+    obj._metadata = "old_style"
+
+    serialized = fory.serialize(obj)
+    deserialized = fory.deserialize(serialized)
+
+    # Check that StatefulSerializer is being used
+    serializer = fory.type_resolver.get_serializer(ImmutableWithArgs)
+    assert isinstance(serializer, StatefulSerializer)
+
+    # Check that constructor arguments were used correctly
+    assert deserialized._a == 100
+    assert deserialized._b == 200
+    assert deserialized._metadata == "old_style"  # Set by __setstate__
+    assert obj == deserialized
+
+
+def test_stateful_only_object():
+    """Test object with only state methods, no constructor args"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    obj = StatefulOnlyObject("test_data")
+    # Simulate the state that would be set by __setstate__ for comparison
+    obj.processed = "restored_test_data"
+
+    serialized = fory.serialize(obj)
+    deserialized = fory.deserialize(serialized)
+
+    # Check that StatefulSerializer is being used
+    serializer = fory.type_resolver.get_serializer(StatefulOnlyObject)
+    assert isinstance(serializer, StatefulSerializer)
+
+    # Check deserialization correctness
+    assert deserialized.data == "test_data"
+    assert deserialized.processed == "restored_test_data"  # Changed by __setstate__
+    assert obj == deserialized
+
+
+def test_complex_state_object():
+    """Test object with a complex nested state"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    obj = ComplexStateObject("test", [1, 2, 3, {"nested": "value"}])
+    # Simulate the state that would be set by __setstate__ for comparison
+    obj.extra_info = {"serialized_at": "test_time"}
+
+    serialized = fory.serialize(obj)
+    deserialized = fory.deserialize(serialized)
+
+    # Check that StatefulSerializer is being used
+    serializer = fory.type_resolver.get_serializer(ComplexStateObject)
+    assert isinstance(serializer, StatefulSerializer)
+
+    # Check deserialization correctness
+    assert deserialized.name == "test"
+    assert deserialized.items == [1, 2, 3, {"nested": "value"}]
+    assert deserialized.count == 4
+    assert deserialized.extra_info == {"serialized_at": "test_time"}
+    assert obj == deserialized
+
+
+def test_reference_tracking():
+    """Test that reference tracking works with StatefulSerializer"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    obj = BasicStatefulObject(42)
+    # Create a list with the same object referenced twice
+    container = [obj, obj, {"ref": obj}]
+
+    serialized = fory.serialize(container)
+    deserialized = fory.deserialize(serialized)
+
+    # Check that references are preserved
+    assert deserialized[0] is deserialized[1]
+    assert deserialized[0] is deserialized[2]["ref"]
+    assert deserialized[0].value == 42
+
+
+def test_nested_stateful_objects():
+    """Test serialization of nested stateful objects"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
+
+    inner = BasicStatefulObject(10)
+    outer = ComplexStateObject("outer", [inner, BasicStatefulObject(20)])
+
+    serialized = fory.serialize(outer)
+    deserialized = fory.deserialize(serialized)
+
+    # Check that nested objects are correctly deserialized
+    assert deserialized.name == "outer"
+    assert len(deserialized.items) == 2
+    assert isinstance(deserialized.items[0], BasicStatefulObject)
+    assert isinstance(deserialized.items[1], BasicStatefulObject)
+    assert deserialized.items[0].value == 10
+    assert deserialized.items[1].value == 20
+
+
+def test_cross_language_compatibility():
+    """Test that StatefulSerializer works with type registration"""
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=True)
+
+    # Register the type explicitly
+    fory.register_type(BasicStatefulObject)
+
+    obj = BasicStatefulObject(42)
+    serialized = fory.serialize(obj)
+
+    # Deserialize with a new Fory instance that also has the type registered
+    fory_new = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=True)
+    fory_new.register_type(BasicStatefulObject)
+    deserialized = fory_new.deserialize(serialized)
+
+    assert deserialized.value == 42
+    assert obj == deserialized
+
+
+if __name__ == "__main__":
+    pytest.main([__file__, "-v"])
diff --git a/python/pyfory/tests/test_struct.py b/python/pyfory/tests/test_struct.py
index 5be25cb45..e212cec04 100644
--- a/python/pyfory/tests/test_struct.py
+++ b/python/pyfory/tests/test_struct.py
@@ -104,19 +104,14 @@ def test_inheritance():
     type_hints = typing.get_type_hints(ChildClass1)
     print(type_hints)
     assert type_hints.keys() == {"f1", "f2", "f3"}
-    fory = Fory(
-        language=Language.PYTHON, ref_tracking=True, require_type_registration=False
-    )
+    fory = Fory(language=Language.PYTHON, ref_tracking=True, require_type_registration=False)
     obj = ChildClass1(f1="a", f2=-10, f3={"a": -10.0, "b": 1 / 3})
     assert ser_de(fory, obj) == obj
-    assert (
-        type(fory.type_resolver.get_serializer(ChildClass1))
-        == pyfory.DataClassSerializer
-    )
+    assert type(fory.type_resolver.get_serializer(ChildClass1)) is pyfory.DataClassSerializer
 
 
 @dataclass
-class TestDataClassObject:
+class DataClassObject:
     f_int: int
     f_float: float
     f_str: str
@@ -130,7 +125,7 @@ class TestDataClassObject:
 def test_data_class_serializer_xlang():
     fory = Fory(language=Language.XLANG, ref_tracking=True)
     fory.register_type(ComplexObject, typename="example.ComplexObject")
-    fory.register_type(TestDataClassObject, typename="example.TestDataClassObject")
+    fory.register_type(DataClassObject, typename="example.TestDataClassObject")
 
     complex_data = ComplexObject(
         f1="nested_str",
@@ -138,7 +133,7 @@ def test_data_class_serializer_xlang():
         f8=3.14,
         f10={10: 1.0, 20: 2.0},
     )
-    obj_original = TestDataClassObject(
+    obj_original = DataClassObject(
         f_int=123,
         f_float=45.67,
         f_str="hello xlang",
@@ -160,21 +155,18 @@ def test_data_class_serializer_xlang():
     assert obj_deserialized.f_dict == obj_original.f_dict
     assert obj_deserialized.f_any == obj_original.f_any
     assert obj_deserialized.f_complex == obj_original.f_complex
-    assert (
-        type(fory.type_resolver.get_serializer(TestDataClassObject))
-        == pyfory.DataClassSerializer
-    )
-    # Ensure it's using xlang mode (indirectly, by checking no JIT methods if possible,
+    assert type(fory.type_resolver.get_serializer(DataClassObject)) is pyfory.DataClassSerializer
+    # Ensure it's using xlang mode indirectly, by checking no JIT methods if possible,
     # or by ensuring it was registered with _register_xtype which now uses DataClassSerializer(xlang=True)
     # For now, the registration path check is implicit via Language.XLANG usage.
     # We can also check if the hash is non-zero if it was computed,
-    # or if _serializers attribute exists.
-    serializer_instance = fory.type_resolver.get_serializer(TestDataClassObject)
+    # or if the _serializers attribute exists.
+    serializer_instance = fory.type_resolver.get_serializer(DataClassObject)
     assert hasattr(serializer_instance, "_serializers")  # xlang mode creates this
     assert serializer_instance._xlang is True
 
-    # Test with None for complex field
-    obj_with_none_complex = TestDataClassObject(
+    # Test with None for a complex field
+    obj_with_none_complex = DataClassObject(
         f_int=789,
         f_float=12.34,
         f_str="another string",


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org