From 5fd72f7f67e01dd4272069130e13ab1b83d20768 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Sat, 28 Feb 2026 02:58:17 +0530 Subject: [PATCH 1/5] fix(python): correct type dispatch in infer_field for custom classes --- python/pyfory/type_util.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/python/pyfory/type_util.py b/python/pyfory/type_util.py index 2b24510272..866a4dd563 100644 --- a/python/pyfory/type_util.py +++ b/python/pyfory/type_util.py @@ -249,11 +249,20 @@ def infer_field(field_name, type_, visitor: TypeVisitor, types_path=None): else: raise TypeError(f"Collection types should be {list, dict} instead of {type_}") else: - if is_function(origin) or not hasattr(origin, "__annotations__"): + if is_function(origin): return visitor.visit_other(field_name, type_, types_path=types_path) - else: + + if origin in (list, dict, set): + return visitor.visit_other(field_name, type_, types_path=types_path) + + if ( + inspect.isclass(origin) + and origin.__module__ not in ("builtins", "datetime") + ): return visitor.visit_customized(field_name, type_, types_path=types_path) + return visitor.visit_other(field_name, type_, types_path=types_path) + def is_function(func): return inspect.isfunction(func) or is_cython_function(func) From 1a5a0af4102e3fbc25e2ec17d4d601efd1ebfc9d Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Sat, 28 Feb 2026 04:00:03 +0530 Subject: [PATCH 2/5] chore(python): apply code style formatting --- python/pyfory/type_util.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/python/pyfory/type_util.py b/python/pyfory/type_util.py index 866a4dd563..4d995d75c3 100644 --- a/python/pyfory/type_util.py +++ b/python/pyfory/type_util.py @@ -254,11 +254,8 @@ def infer_field(field_name, type_, visitor: TypeVisitor, types_path=None): if origin in (list, dict, set): return visitor.visit_other(field_name, type_, types_path=types_path) - - if ( - inspect.isclass(origin) - and origin.__module__ not in ("builtins", "datetime") - ): + + if inspect.isclass(origin) and origin.__module__ not in ("builtins", "datetime"): return visitor.visit_customized(field_name, type_, types_path=types_path) return visitor.visit_other(field_name, type_, types_path=types_path) From 26abcde3ffe4a03c980145692f57f7666cc3c80b Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Sat, 28 Feb 2026 16:00:14 +0530 Subject: [PATCH 3/5] test(python): add regression tests for custom class type inference --- python/pyfory/format/tests/test_infer.py | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/python/pyfory/format/tests/test_infer.py b/python/pyfory/format/tests/test_infer.py index ea3a517c00..744452b418 100644 --- a/python/pyfory/format/tests/test_infer.py +++ b/python/pyfory/format/tests/test_infer.py @@ -63,6 +63,39 @@ class X: result = _infer_field("", X) assert result.type.id == TypeId.STRUCT +def test_infer_field_unannotated_class(): + """Classes without annotations should be treated as structs, not raise TypeError.""" + + class Empty: + pass + + class WithInit: + def __init__(self): + self.x = 1 + self.y = "hello" + + # Should not raise TypeError and must be treated as STRUCT + assert _infer_field("", Empty).type.id == TypeId.STRUCT + assert _infer_field("", WithInit).type.id == TypeId.STRUCT + + +def test_infer_field_builtin_types_not_treated_as_struct(): + """Built-in types must NOT be routed to visit_customized (regression guard).""" + assert _infer_field("", int).type.id == TypeId.INT64 + assert _infer_field("", float).type.id == TypeId.FLOAT64 + assert _infer_field("", str).type.id == TypeId.STRING + assert _infer_field("", bytes).type.id == TypeId.BINARY + assert _infer_field("", bool).type.id == TypeId.BOOL + + +def test_infer_field_nested_custom_class(): + """Custom class nested inside a List should also be handled correctly.""" + + class Inner: + pass + + result = _infer_field("", List[Inner]) + assert result.type.id == TypeId.LIST def test_infer_class_schema(): schema = infer_schema(Foo) From 629a13ba268f4683e4eed5c47075304095f430ea Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Sat, 28 Feb 2026 16:11:53 +0530 Subject: [PATCH 4/5] chore(python): apply code style formatting --- python/pyfory/format/tests/test_infer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/pyfory/format/tests/test_infer.py b/python/pyfory/format/tests/test_infer.py index 744452b418..73b1846adb 100644 --- a/python/pyfory/format/tests/test_infer.py +++ b/python/pyfory/format/tests/test_infer.py @@ -63,6 +63,7 @@ class X: result = _infer_field("", X) assert result.type.id == TypeId.STRUCT + def test_infer_field_unannotated_class(): """Classes without annotations should be treated as structs, not raise TypeError.""" @@ -95,7 +96,8 @@ class Inner: pass result = _infer_field("", List[Inner]) - assert result.type.id == TypeId.LIST + assert result.type.id == TypeId.LIST + def test_infer_class_schema(): schema = infer_schema(Foo) From 5595d01724aafa00891b853720ad25e85b3277e2 Mon Sep 17 00:00:00 2001 From: Surya Srinivasan Date: Sat, 28 Feb 2026 16:23:06 +0530 Subject: [PATCH 5/5] test(python): adjust regression tests per dataclass-only struct design --- python/pyfory/format/tests/test_infer.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/python/pyfory/format/tests/test_infer.py b/python/pyfory/format/tests/test_infer.py index 73b1846adb..76f60b26b4 100644 --- a/python/pyfory/format/tests/test_infer.py +++ b/python/pyfory/format/tests/test_infer.py @@ -64,22 +64,6 @@ class X: assert result.type.id == TypeId.STRUCT -def test_infer_field_unannotated_class(): - """Classes without annotations should be treated as structs, not raise TypeError.""" - - class Empty: - pass - - class WithInit: - def __init__(self): - self.x = 1 - self.y = "hello" - - # Should not raise TypeError and must be treated as STRUCT - assert _infer_field("", Empty).type.id == TypeId.STRUCT - assert _infer_field("", WithInit).type.id == TypeId.STRUCT - - def test_infer_field_builtin_types_not_treated_as_struct(): """Built-in types must NOT be routed to visit_customized (regression guard).""" assert _infer_field("", int).type.id == TypeId.INT64