diff --git a/jsonversation/aio/models.py b/jsonversation/aio/models.py index 5b32300..8b01fac 100644 --- a/jsonversation/aio/models.py +++ b/jsonversation/aio/models.py @@ -31,7 +31,17 @@ def __init__(self) -> None: self._parsed_keys = [] self._value = {} - for key, type_hint in type(self).__annotations__.items(): + # initialize keys for potential parent classes + for cls in self.__class__.mro()[1:-1]: + if cls.__name__ == "Object" or cls.__name__ == "StreamingObject": + break + + self._initialize_attributes(cls.__annotations__) + + self._initialize_attributes(type(self).__annotations__) + + def _initialize_attributes(self, attributes: dict[str, Any]) -> None: + for key, type_hint in attributes.items(): self._keys.append(key) # Handle List[T] diff --git a/jsonversation/sync/models.py b/jsonversation/sync/models.py index 2bbfb35..715fbec 100644 --- a/jsonversation/sync/models.py +++ b/jsonversation/sync/models.py @@ -5,16 +5,23 @@ class StreamingObject[T]: + _on_complete_funcs: list[Callable[[T], None]] + + def __init__(self) -> None: + self._on_complete_funcs = [] + def update(self, value: T) -> None: return None def _complete(self) -> None: ... + def on_complete(self, func: Callable[[T], None]) -> None: + self._on_complete_funcs.append(func) + class Object(StreamingObject[dict[str, Any]]): _keys: list[str] _parsed_keys: list[str] - _on_complete_funcs: list[Callable[[dict[str, Any]], None]] _value: dict[str, Any] def __init__(self) -> None: @@ -22,9 +29,18 @@ def __init__(self) -> None: self._keys = [] self._parsed_keys = [] self._value = {} - self._on_complete_funcs = [] - for key, type_hint in type(self).__annotations__.items(): + # initialize keys for potential parent classes + for cls in self.__class__.mro()[1:-1]: + if cls.__name__ == "Object" or cls.__name__ == "StreamingObject": + break + + self._initialize_attributes(cls.__annotations__) + + self._initialize_attributes(type(self).__annotations__) + + def _initialize_attributes(self, attributes: dict[str, Any]) -> None: + for key, type_hint in attributes.items(): self._keys.append(key) # Handle List[T] @@ -167,23 +183,19 @@ def get_value(self) -> list[T]: return self._values -class Atomic[T](StreamingObject[T]): +class Atomic[T](StreamingObject[T | None]): _is_empty: bool _value: T | None - _on_complete_funcs: list[Callable[[T | None], None]] def __init__(self, item_cls: type[T]) -> None: self._is_empty = True self._value = None self._on_complete_funcs = [] - def update(self, value: T) -> None: + def update(self, value: T | None) -> None: self._value = value self._is_empty = False - def on_complete(self, func: Callable[[T | None], None]) -> None: - self._on_complete_funcs.append(func) - def _complete(self) -> None: if not self._is_empty: for func in self._on_complete_funcs: diff --git a/pyproject.toml b/pyproject.toml index a52d2ed..7417a42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "jsonversation" -version = "0.1.2" +version = "0.1.3" description = "Fast and type-safe JSON stream parser" authors = [{name = "Vetted.ai Engineering", email = "engineering@vetted.ai"}] readme = "README.md" diff --git a/tests/aio/models/test_object.py b/tests/aio/models/test_object.py index b3dd541..6d45c53 100644 --- a/tests/aio/models/test_object.py +++ b/tests/aio/models/test_object.py @@ -763,3 +763,15 @@ async def footer_callback(value: str) -> None: assert footer_completed == ["Stream Footer"] # Completed on final _complete() assert len(completed_values) == 1 + + +def test_object_creation_with_parent_classes() -> None: + class ParentObject(jv.Object): + name: jv.String + + class ChildObject(ParentObject): + test_field: jv.String + + o = ChildObject() + + assert o.__getattribute__("name") is not None diff --git a/tests/sync/models/test_object.py b/tests/sync/models/test_object.py index 32abad1..3570c98 100644 --- a/tests/sync/models/test_object.py +++ b/tests/sync/models/test_object.py @@ -755,3 +755,15 @@ def footer_callback(value: str) -> None: assert footer_completed == ["Stream Footer"] # Completed on final _complete() assert len(completed_values) == 1 + + +def test_object_creation_with_parent_classes() -> None: + class ParentObject(jv.Object): + name: jv.String + + class ChildObject(ParentObject): + test_field: jv.String + + o = ChildObject() + + assert o.__getattribute__("name") is not None diff --git a/uv.lock b/uv.lock index 722a8fa..286fe82 100644 --- a/uv.lock +++ b/uv.lock @@ -103,7 +103,7 @@ wheels = [ [[package]] name = "jsonversation" -version = "0.1.0" +version = "0.1.3" source = { virtual = "." } dependencies = [ { name = "jiter" },