Created
October 3, 2025 16:22
-
-
Save MtkN1/5a237bd10ea8db3c804bbeb3203744b8 to your computer and use it in GitHub Desktop.
Unstructuring the dataclass
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from __future__ import annotations | |
| from collections.abc import Mapping, Sequence | |
| from dataclasses import dataclass, field, fields, is_dataclass | |
| from typing import TYPE_CHECKING, Any, cast | |
| import pytest | |
| if TYPE_CHECKING: | |
| from _typeshed import DataclassInstance | |
| def unstructure_dataclass( | |
| obj: DataclassInstance, /, *, by_alias: bool = True | |
| ) -> dict[str, Any]: | |
| result: dict[str, Any] = {} | |
| for dataclass_field in fields(obj): | |
| alias = dataclass_field.metadata.get("alias") if by_alias else None | |
| key = alias or dataclass_field.name | |
| value = getattr(obj, dataclass_field.name) | |
| result[key] = value | |
| return result | |
| def unstructure(obj: Any, /, *, by_alias: bool = True) -> Any: | |
| if is_dataclass(obj) and not isinstance(obj, type): | |
| shallow = unstructure_dataclass(obj, by_alias=by_alias) | |
| return unstructure(shallow, by_alias=by_alias) | |
| if isinstance(obj, Mapping): | |
| mapping_obj = cast(Mapping[Any, Any], obj) | |
| return { | |
| key: unstructure(value, by_alias=by_alias) | |
| for key, value in mapping_obj.items() | |
| } | |
| if isinstance(obj, Sequence) and not isinstance(obj, (str, bytes, bytearray)): | |
| sequence_obj = cast(Sequence[Any], obj) | |
| return [unstructure(item, by_alias=by_alias) for item in sequence_obj] | |
| return obj | |
| @dataclass(frozen=True, kw_only=True, slots=True) | |
| class User: | |
| name: str = field(metadata={"alias": "userName"}) | |
| age: int | |
| @dataclass(frozen=True, kw_only=True, slots=True) | |
| class Post: | |
| message: str | |
| author: User | |
| @pytest.mark.parametrize( | |
| ("test_input", "expected"), | |
| [ | |
| pytest.param( | |
| # test_input | |
| ( | |
| User(name="Alice", age=18), # obj | |
| True, # by_alias | |
| ), | |
| # expected | |
| {"userName": "Alice", "age": 18}, | |
| id="by_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| User(name="Alice", age=18), # obj | |
| False, # by_alias | |
| ), | |
| # expected | |
| {"name": "Alice", "age": 18}, | |
| id="without_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| Post(message="hello, world", author=User(name="Alice", age=18)), # obj | |
| True, # by_alias | |
| ), | |
| # expected | |
| {"message": "hello, world", "author": User(name="Alice", age=18)}, | |
| id="nested-by_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| Post(message="hello, world", author=User(name="Alice", age=18)), # obj | |
| False, # by_alias | |
| ), | |
| # expected | |
| {"message": "hello, world", "author": User(name="Alice", age=18)}, | |
| id="nested-without_alias", | |
| ), | |
| ], | |
| ) | |
| def test_unstructure_dataclass( | |
| test_input: tuple[DataclassInstance, bool], expected: dict[str, Any] | |
| ) -> None: | |
| obj, by_alias = test_input | |
| maybe_user = unstructure_dataclass(obj, by_alias=by_alias) | |
| assert maybe_user == expected | |
| @pytest.mark.parametrize( | |
| ("test_input", "expected"), | |
| [ | |
| pytest.param( | |
| # test_input | |
| ( | |
| User(name="Alice", age=18), # obj | |
| True, # by_alias | |
| ), | |
| # expected | |
| {"userName": "Alice", "age": 18}, | |
| id="by_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| User(name="Alice", age=18), # obj | |
| False, # by_alias | |
| ), | |
| # expected | |
| {"name": "Alice", "age": 18}, | |
| id="without_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| Post(message="hello, world", author=User(name="Alice", age=18)), # obj | |
| True, # by_alias | |
| ), | |
| # expected | |
| {"message": "hello, world", "author": {"userName": "Alice", "age": 18}}, | |
| id="nested-by_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| Post(message="hello, world", author=User(name="Alice", age=18)), # obj | |
| False, # by_alias | |
| ), | |
| # expected | |
| {"message": "hello, world", "author": {"name": "Alice", "age": 18}}, | |
| id="nested-without_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| [User(name="Alice", age=18), User(name="Bob", age=20)], # obj | |
| True, # by_alias | |
| ), | |
| # expected | |
| [ | |
| {"userName": "Alice", "age": 18}, | |
| {"userName": "Bob", "age": 20}, | |
| ], | |
| id="sequence-by_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| [User(name="Alice", age=18), User(name="Bob", age=20)], # obj | |
| False, # by_alias | |
| ), | |
| # expected | |
| [ | |
| {"name": "Alice", "age": 18}, | |
| {"name": "Bob", "age": 20}, | |
| ], | |
| id="sequence-without_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| {"user": User(name="Alice", age=18)}, # obj | |
| True, # by_alias | |
| ), | |
| # expected | |
| {"user": {"userName": "Alice", "age": 18}}, | |
| id="mapping-by_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| {"user": User(name="Alice", age=18)}, # obj | |
| False, # by_alias | |
| ), | |
| # expected | |
| {"user": {"name": "Alice", "age": 18}}, | |
| id="mapping-without_alias", | |
| ), | |
| pytest.param( | |
| # test_input | |
| ( | |
| 42, # obj | |
| True, # by_alias | |
| ), | |
| # expected | |
| 42, | |
| id="primitive", | |
| ), | |
| ], | |
| ) | |
| def test_unstructure(test_input: tuple[object, bool], expected: dict[str, Any]) -> None: | |
| obj, by_alias = test_input | |
| actual = unstructure(obj, by_alias=by_alias) | |
| assert actual == expected |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
AGENTS.md