Heuristic:Dagster io Dagster Record Over Dataclass
| Knowledge Sources | |
|---|---|
| Domains | Code_Style, Data_Structures |
| Last Updated | 2026-02-10 12:00 GMT |
Overview
Mandatory convention to use @record instead of @dataclass for all data structures in the Dagster codebase.
Description
The Dagster codebase uses a custom @record decorator (from dagster_shared.record) as its sole data structure pattern, replacing Python's @dataclass. Records are immutable by default, support structural equality, and provide a consistent with_* method pattern for creating modified copies. This is a strict coding convention that is enforced across the entire codebase.
Usage
Use this heuristic whenever creating new data structures in the Dagster codebase. If you see a @dataclass in new code, it should be converted to @record. The convention also applies when modifying existing data structures or reviewing pull requests.
The Insight (Rule of Thumb)
- Action: Use
@recordfromdagster_shared.recordfor ALL data structures. NEVER use@dataclass. - Value: Provides immutability by default, structural equality, and consistent mutation patterns across the codebase.
- Trade-off: Slight learning curve for contributors unfamiliar with the custom
@recordAPI. Cannot use dataclass-specific features like__post_init__. - Mutation pattern: Use
with_*methods for creating modified copies (e.g.,result.with_error(err)).
Reasoning
The @record decorator was introduced to address several issues with @dataclass:
- Immutability by default: Dataclasses are mutable by default (
frozen=False). Dagster needs immutable data structures for safe concurrent access and caching. - Consistent API: The
with_*naming convention provides a clear, discoverable pattern for creating modified copies without mutating the original. - Serialization: Records integrate with Dagster's serialization framework (
whitelist_for_serdes) more cleanly than dataclasses. - NamedTuple migration: The codebase historically used
NamedTupleextensively.@recordprovides a modern replacement that maintains immutability guarantees.
Code Evidence
From .claude/coding_conventions.md:
# CORRECT - Use @record
from dagster_shared.record import record
@record
class AssetConditionSnapshot:
condition_type: str
description: str
unique_id: str
# Create modified copies with with_* methods
snapshot = AssetConditionSnapshot(
condition_type="eager",
description="...",
unique_id="abc123"
)
# WRONG - Do NOT use @dataclass
from dataclasses import dataclass
@dataclass
class AssetConditionSnapshot: # This will be rejected in code review
condition_type: str
description: str
unique_id: str