Dataclass runtime machinery for declarative DSLs
Project description
dataclass-dsl
Dataclass runtime machinery for declarative DSLs using the no-parens pattern.
The No-Parens Pattern
The no-parens pattern enables declarative references without function calls:
# Traditional approach (requires parentheses)
parent = get_ref(Parent)
parent_id = get_attr(Parent, "Id")
# No-parens pattern (cleaner, more declarative)
parent = Parent # Direct class reference
parent_id = Parent.Id # Attribute reference
This library provides the runtime machinery to enable this pattern in dataclass-based DSLs.
Type Annotations with Annotated
For type-safe references, use typing.Annotated with the provided markers:
from typing import Annotated
from dataclass_dsl import Ref, Attr, create_decorator
refs = create_decorator()
@refs
class Network:
cidr: str = "10.0.0.0/16"
@refs
class Subnet:
# Type checker sees Network, frameworks see Ref() marker
network: Annotated[Network, Ref()] = None
# Type checker sees str, frameworks see Attr(Gateway, "Id")
gateway_id: Annotated[str, Attr(Gateway, "Id")] = ""
Installation
pip install dataclass-dsl
Quick Start
Basic Usage
from dataclass_dsl import create_decorator, ResourceRegistry
# Create a domain-specific decorator
registry = ResourceRegistry()
refs = create_decorator(registry=registry)
@refs
class Object1:
name = "object-1" # Type inferred as str
@refs
class Object2:
# No-parens pattern - types inferred from defaults
parent = Object1
parent_id = Object1.Id
Multi-File Package with from . import *
The primary use case is organizing resources across multiple files:
mypackage/objects/__init__.py
from dataclass_dsl import setup_resources, StubConfig
stub_config = StubConfig(
package_name="mypackage",
core_imports=["refs", "Object1", "Object2"],
)
setup_resources(__file__, __name__, globals(), stub_config=stub_config)
mypackage/objects/object1.py
from . import * # refs available via setup_resources()
@refs
class Object1:
name = "object-1"
mypackage/objects/object2.py
from . import * # refs, Object1 available via setup_resources()
@refs
class Object2:
# No-parens pattern - reference Object1 without imports
parent = Object1
parent_id = Object1.Id
Usage:
from mypackage.objects import * # All objects available
obj = Object2()
print(obj.parent) # <class 'Object1'>
print(obj.parent_id) # AttrRef(Object1, 'Id')
Core Features
No-Parens Class References
Reference another decorated class directly by name:
@refs
class Object1:
value = "base"
@refs
class Object2:
# No-parens: Object1 becomes a field default
parent = Object1
obj = Object2()
assert obj.parent is Object1
No-Parens Attribute References (AttrRef)
Access attributes on decorated classes to create AttrRef markers:
@refs
class Object1:
name = "object-1"
@refs
class Object2:
# No-parens: Object1.Id returns AttrRef(Object1, "Id")
parent_id = Object1.Id
obj = Object2()
assert obj.parent_id.target is Object1
assert obj.parent_id.attr == "Id"
Dependency Detection
The library detects dependencies from both patterns:
from dataclass_dsl import get_all_dependencies
deps = get_all_dependencies(Object2)
assert Object1 in deps
Topological Ordering
Sort objects by their dependencies for creation/deletion:
from dataclass_dsl import get_creation_order, get_deletion_order
@refs
class Object1:
name = "base"
@refs
class Object2:
parent = Object1
@refs
class Object3:
parent = Object2
# Dependencies first (for creation)
order = get_creation_order([Object3, Object2, Object1])
# -> [Object1, Object2, Object3]
# Dependents first (for deletion)
order = get_deletion_order([Object3, Object2, Object1])
# -> [Object3, Object2, Object1]
Resource Registry
Track decorated classes for discovery:
registry = ResourceRegistry()
refs = create_decorator(registry=registry)
@refs
class Object1:
name = "base"
@refs
class Object2:
parent = Object1
@refs
class Object3:
parent = Object2
all_objects = registry.get_all()
assert Object1 in all_objects
assert Object2 in all_objects
assert Object3 in all_objects
Template Aggregation
Collect and serialize objects:
from dataclass_dsl import Template
template = Template.from_registry(
registry=registry,
description="My objects",
)
# Objects returned in dependency order
for obj in template.get_dependency_order():
print(type(obj).__name__)
Provider Pattern
Abstract interface for domain-specific serialization:
from dataclass_dsl import Provider
class MyProvider(Provider):
name = "myformat"
def serialize_ref(self, source, target):
return {"ref": target.__name__}
def serialize_attr(self, source, target, attr_name):
return {"attr": f"{target.__name__}.{attr_name}"}
def serialize_resource(self, resource):
return {"type": type(resource).__name__}
output = template.to_dict(provider=MyProvider())
IDE Stub Generation
Generate .pyi files for IDE autocomplete with dynamic imports:
from dataclass_dsl import StubConfig, generate_stub_file
config = StubConfig(
package_name="mypackage",
core_imports=["refs", "Object1", "Object2"],
)
generate_stub_file(package_path, config=config)
API Reference
Core
AttrRef- Runtime marker for attribute references (Object1.Id)RefMeta- Metaclass enabling no-parens attribute interceptioncreate_decorator()- Factory for domain-specific decorators
Registry
ResourceRegistry- Thread-safe registry for decorated classes
Ordering
get_all_dependencies()- Get all dependencies of a classtopological_sort()- Sort by dependency orderget_creation_order()- Dependencies firstget_deletion_order()- Dependents firstdetect_cycles()- Find circular dependenciesget_dependency_graph()- Build adjacency list
Provider
Provider- Abstract base class for serialization
Template
Template- Base class for object aggregation
Loader
setup_resources()- Import modules in dependency order forfrom . import *
Stubs
StubConfig- Configuration for stub generationgenerate_stub_file()- Generate.pyifor IDE support
Helpers
is_attr_ref()- Check if object is AttrRefis_class_ref()- Check if object is decorated classget_ref_target()- Extract target from referenceapply_metaclass()- Apply metaclass to existing class
Type Markers (Annotated-based)
Ref- Marker for reference relationship (Annotated[T, Ref()])Attr- Marker for attribute reference (Annotated[str, Attr(T, "name")])RefList- Marker for list of referencesRefDict- Marker for dict with reference valuesContextRef- Marker for context referenceRefInfo- Metadata about a reference fieldget_refs()- Extract reference info from type hintsget_dependencies()- Get dependency classes from type hints
Design Rationale
Why Two Patterns?
dataclass-dsl provides two complementary mechanisms for expressing references:
- No-parens pattern (
parent = Parent) - Runtime detection - Annotated markers (
parent: Annotated[Parent, Ref()]) - Static type checking
These serve different purposes:
| Concern | No-Parens | Annotated |
|---|---|---|
| Serialization | ✓ Detected from class defaults | ✓ Detected from type hints |
| Dependency ordering | ✓ is_class_ref(default) |
✓ get_dependencies(cls) |
| IDE autocomplete | ✗ Sees type[Parent] |
✓ Sees Parent directly |
| Type error detection | ✗ No static checking | ✓ Wrong type = red squiggle |
| Refactoring support | ✗ String-like | ✓ Rename propagates |
When No-Parens Is Sufficient
For declarative class definitions with class-level defaults, no-parens handles everything at runtime:
@refs
class MyFunction:
role = MyRole # Detected as dependency
role_arn = MyRole.Arn # Detected as attribute reference
The runtime can detect MyRole is a dependency because it is the value.
When Annotated Markers Add Value
1. IDE Type Checking - Critical for catching errors before runtime:
@refs
class MyFunction:
# IDE shows error if you pass a Bucket where Role expected
role: Annotated[MyRole, Ref()] = MyRole
2. Imperative Style - When constructing instances programmatically:
# Without annotation, IDE can't validate this
function = MyFunction(role=some_role)
# With annotation on the class, IDE catches type mismatches
role: Annotated[MyRole, Ref()]
3. Forward References - When the target class isn't defined yet:
from __future__ import annotations
@refs
class MyFunction:
# Can reference MyRole before it's defined
role: Annotated[MyRole, Ref()] = None
@refs
class MyRole:
name = "my-role"
The Bottom Line
- No-parens handles runtime behavior (serialization, dependency detection)
- Annotated markers handle static analysis (IDE support, type checking)
If IDE type checking is important to your workflow, use both. The runtime detection ensures serialization works regardless of annotations, while annotations give you the IDE experience.
The No-Parens Pattern in Detail
The no-parens pattern works through two mechanisms:
-
RefMeta Metaclass: Intercepts attribute access on decorated classes. When you access an undefined attribute like
Object1.Id, it returnsAttrRef(Object1, "Id")instead of raisingAttributeError. -
Decorator Processing: The
create_decorator()factory creates decorators that:- Apply
@dataclasstransformation - Apply
RefMetametaclass - Handle class and AttrRef defaults as dataclass fields
- Register classes with the optional registry
- Apply
This enables clean, declarative DSLs where relationships between objects are expressed directly through class names rather than function calls.
License
Apache 2.0
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dataclass_dsl-0.1.2.tar.gz.
File metadata
- Download URL: dataclass_dsl-0.1.2.tar.gz
- Upload date:
- Size: 40.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f24fcb2bd794f43ffed6024e93442910d6da70f86de2342556ef822111cda68c
|
|
| MD5 |
b9d06e8565c9d43327a9771d163a71d7
|
|
| BLAKE2b-256 |
ddbf1e0a0f3d9f06a166dc9d1bcadace183f8fe014b22359c3c012326b831b5e
|
Provenance
The following attestation bundles were made for dataclass_dsl-0.1.2.tar.gz:
Publisher:
release.yml on lex00/dataclass-dsl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dataclass_dsl-0.1.2.tar.gz -
Subject digest:
f24fcb2bd794f43ffed6024e93442910d6da70f86de2342556ef822111cda68c - Sigstore transparency entry: 781000067
- Sigstore integration time:
-
Permalink:
lex00/dataclass-dsl@6fd86db6fe738735eff82763e80754a0d9187ac3 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/lex00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6fd86db6fe738735eff82763e80754a0d9187ac3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dataclass_dsl-0.1.2-py3-none-any.whl.
File metadata
- Download URL: dataclass_dsl-0.1.2-py3-none-any.whl
- Upload date:
- Size: 35.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a17aac3dd2c8c875d4ec7180b1380d5c96e8ac71a0e30846dd1046c29385b0d
|
|
| MD5 |
a05bfefc8ada76c760024486020afdcd
|
|
| BLAKE2b-256 |
9b7760cb6658ccec2941ab5840dd8332225cce0e06c553d9232fab88dc99ce21
|
Provenance
The following attestation bundles were made for dataclass_dsl-0.1.2-py3-none-any.whl:
Publisher:
release.yml on lex00/dataclass-dsl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dataclass_dsl-0.1.2-py3-none-any.whl -
Subject digest:
7a17aac3dd2c8c875d4ec7180b1380d5c96e8ac71a0e30846dd1046c29385b0d - Sigstore transparency entry: 781000070
- Sigstore integration time:
-
Permalink:
lex00/dataclass-dsl@6fd86db6fe738735eff82763e80754a0d9187ac3 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/lex00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6fd86db6fe738735eff82763e80754a0d9187ac3 -
Trigger Event:
release
-
Statement type: