Skip to main content

deep_dataclasses

Tests Coverage Docs PyPI Python License Size

Define nested dataclass hierarchies as clean, readable schemas — no boilerplate, no dependencies.


The Problem

Python's @dataclass requires you to define each level of a nested hierarchy separately, then wire them together manually. Each inner class name must appear three times: once in the class definition, once as the field type hint, and once in field(default_factory=...):

from dataclasses import dataclass, asdict, field

@dataclass
class NestedParent:
    @dataclass
    class Child:
        @dataclass
        class GrandChild:
            grandchild_str: str = "grandchild1"
            grandchild_num: int = 1

        grandchild: GrandChild = field(default_factory=GrandChild)
        child_str: str = "child"

    child: Child = field(default_factory=Child)
    parent_str: str = "parent"

And even after all that boilerplate, the asdict round-trip is broken:

NestedParent(**asdict(NestedParent())) == NestedParent()  # False

This is because asdict serialises nested instances to plain dicts, but @dataclass does not coerce them back on construction — unlike flat dataclasses where this works naturally.


The Solution

@deep_dataclass lets you express the same hierarchy as a natural nested schema. The decorator infers the class name, type hint, and default_factory from the nested block — no repetition:

from deep_dataclasses import deep_dataclass

@deep_dataclass(autosnake=True)
class DeepParent:
    class Child:
        class Grandchild:
            grandchild_str: str = "grandchild1"
            grandchild_num: int = 1
        child_str: str = "child"
    parent_str: str = "parent"

print(DeepParent().child.grandchild)
# Grandchild(grandchild_str='grandchild1', grandchild_num=1)

The autosnake=True option converts PascalCase inner class names to snake_case field names. Without it the field name matches the class name exactly.


Fully Compatible with dataclasses

@deep_dataclass produces standard dataclass instances — all stdlib tools work as expected, and the asdict round-trip is fixed:

d1 = NestedParent()  # vanilla dataclass hierarchy
d2 = DeepParent()    # deep_dataclass equivalent

asdict(d1) == asdict(d2)        # True — identical structure
DeepParent(**asdict(d2)) == d2  # True — coercion works
NestedParent(**asdict(d1)) == d1  # False — vanilla @dataclass doesn't coerce nested dicts

Validation and Config Loading: A poor mans pydantic

to_json_schema exports any @deep_dataclass schema for use with third-party validators. Because @deep_dataclass coerces nested dicts at construction time, the validate-then-construct pattern works at all depths:

from deep_dataclasses import to_json_schema
import jsonschema, json

raw = json.loads('{"child": {"grandchild": {"grandchild_num": 2}}}')
jsonschema.validate(raw, to_json_schema(DeepParent))  # validate first
cfg = DeepParent(**raw)                               # then construct — fully typed
assert isinstance(cfg.child, DeepParent.child)        # True

Validation catches type violations at any nesting depth:

data = asdict(DeepParent())
data['child']['child_str'] = 3                         # inject a type error
jsonschema.validate(data, to_json_schema(DeepParent))  # raises ValidationError
Failed validating 'type' in schema['properties']['child']['properties']['child_str']:
    {'type': 'string', 'default': 'child'}

On instance['child']['child_str']:
    3

Data Modelling with Type Hints

@deep_dataclass works with the full range of typing annotations. The @auxiliary decorator marks an inner class as a type-only helper — it won't become a standalone field, but can be referenced in Union[...], List[...], Optional[...], etc.

from dataclasses import field, asdict
from typing import Literal, List, Union
from deep_dataclasses import deep_dataclass, auxiliary, to_json_schema
import jsonschema

@deep_dataclass
class Config:
    @auxiliary
    class TrainMode:
        lr: float = 0.001
        pseudo_batch_size: int = 32
    @auxiliary
    class TestMode:
        metric: str = "accuracy"
        folds: int = 5
    mode: Union[TrainMode, TestMode] = field(default_factory=TrainMode)
    device: Literal["cpu", "cuda"] = "cpu"
    images: List[str] = field(default_factory=list)

When constructing from a dict, @deep_dataclass selects the Union variant whose field names best cover the keys supplied — an exact match is always preferred over a partial one:

cfg_train = Config(mode={"lr": 0.05})           # 'lr' is a TrainMode field
cfg_test  = Config(mode={"metric": "f1"})        # 'metric' is a TestMode field

assert isinstance(cfg_train.mode, Config.TrainMode)
assert isinstance(cfg_test.mode,  Config.TestMode)
assert cfg_train.mode.pseudo_batch_size == 32    # unspecified fields get defaults

Schema validation enforces Literal values, Union structure, and List element types:

jsonschema.validate(asdict(Config()), to_json_schema(Config))  # passes

bad = asdict(Config())
bad["device"] = "tpu"                            # not in Literal["cpu", "cuda"]
jsonschema.validate(bad, to_json_schema(Config)) # raises ValidationError

Installation

pip install deep-dataclasses

deep_dataclasses has zero mandatory dependencies — it uses only re, dataclasses, and typing from the standard library. jsonschema (used in the examples above) is an optional third-party package needed only if you want schema validation.


Comparison

Feature @dataclass @deep_dataclass
Nested hierarchy Manual, verbose Inline, readable
field(default_factory=...) Required per field Automatic
Nested dict → instance coercion ❌ ✅ (recursive, all depths)
Union variant selection from dict ❌ ✅ (best-match by field coverage)
asdict() / == / __repr__ ✅ ✅
frozen, slots, etc. ✅ ✅ (tested)
Type validation ❌ Exports to jsonschema
Mandatory dependencies stdlib stdlib only

Status

Early release. Core functionality is complete with 100% test coverage. API may evolve — feedback welcome on discuss.python.org.

Contributing

Issues and PRs welcome. See the issue tracker for known TODOs.

Metadata

Release files for deep-dataclasses 0.3.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for deep-dataclasses 0.3.5
File Size Uploaded Provenance
deep_dataclasses-0.3.5.tar.gz 28.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for deep-dataclasses 0.3.5
File Interpreter ABI Platform
deep_dataclasses-0.3.5-py3-none-any.whl Python 3 none any Details

Total release size: 44.5 kB

Release files / deep_dataclasses-0.3.5.tar.gz

Download URL deep_dataclasses-0.3.5.tar.gz
Size 28.2 kB
Tags Source
SHA-256 checksum
How to use checksums
a7c6361f767296e86cebe0de6aaa6f8ae5165aa94be2a54bd60747ca1fedcf3b
BLAKE2b-256 checksum
How to use checksums
9d952f1a45e2425686e69569c37b9aadf40296d1f7a6e83efe17988a52e2a0ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / deep_dataclasses-0.3.5-py3-none-any.whl

Download URL deep_dataclasses-0.3.5-py3-none-any.whl
Size 16.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
231053cdbbdf51954c20b604b1ed8245aba016453458fb33689fc4a6e72b2a85
BLAKE2b-256 checksum
How to use checksums
ea4b49788302c084b5676d1d3c2e5011f8420535443ad3281d12bbd6c7bd89b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Security for deep-dataclasses 0.3.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page