Skip to content

Instantly share code, notes, and snippets.

@MtkN1
Created October 3, 2025 16:22
Show Gist options
  • Select an option

  • Save MtkN1/5a237bd10ea8db3c804bbeb3203744b8 to your computer and use it in GitHub Desktop.

Select an option

Save MtkN1/5a237bd10ea8db3c804bbeb3203744b8 to your computer and use it in GitHub Desktop.
Unstructuring the dataclass
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
@MtkN1
Copy link
Author

MtkN1 commented Oct 3, 2025

AGENTS.md

# AGENTS.md

## Coding Standards

- Static typing with Pyright.
- Follow typing PEPs.
  - PEP 585 – Type Hinting Generics In Standard Collections
  - PEP 604 – Allow writing union types as `X | Y`
  - PEP 695 – Type Parameter Syntax
  - PEP 696 – Type Defaults for Type Parameters
- Follow SOLID principles.

## Commands

- Python interpreter: `.venv/bin/python` (Python 3.13)
- Test: `.venv/bin/pytest <target files>...`
- Type check: `NPM_CONFIG_CACHE=.cache/npm npx -y pyright <target files>...`

## External Contexts

- `site/`: cloned external repositories.
  - `cpython-3.13/`: python/cpython at branch 3.13.
  - `peps-main/`: python/peps at branch main.
  - `typing-main/`: python/typing at branch main.
- The MCP server `context7` tool may be available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment