Agent skill · posthog

writing-dataclasses

House rules for Python dataclasses in PostHog: when to reach for one instead of a tuple or `dict[str, Any]`, which decorator to use (`@frozen` from `posthog.dataclasses`), how to name, construct, consume and evolve them, how to keep secrets out of `repr`, and when a function should accept a dataclass instead of its unpacked fields. Use when adding or changing a dataclass, returning or passing several values from a function, converting a tuple or dict payload, deciding `frozen=`/`slots=`/`kw_only=`, or passing a facade contract DTO through internal layers. Not for pydantic models used as HogQL/query schema, DRF serializers, or Django models.

What it needs

About 4k tokens when loaded.

What this skill does

Writing dataclasses The point of every rule here is the same: values that share a type get swapped silently, and a positional tuple or a dict[str, Any] lets that happen. A named, frozen dataclass makes the swap a typecheck failure instead of a runtime bug. Everything below follows from that; if a rule doesn't serve it in your case, say so in the PR and skip it. When a dataclass, when not Return or pass a dataclass instead of a tuple when two or more elements share a type ((start, end), (width, height), (rows, columns)), or when the tuple has roughly 3+ elements and positional access hurts readability. A small tuple of clearly different types ((user, count)) is fine as-is. Prefer a dataclass over dict[str, Any] when a fixed set of values crosses a function boundary. A dict key typo fails at runtime; a dataclass field typo fails typecheck. Dicts stay for genuinely dynamic key sets. NamedTuple is not the answer for the swap problem: it still unpacks positionally. Which decorator Use @frozen from posthog.dataclasses for internal value and result objects. It is @dataclass with frozen=True, kwonly=True, slots=True as defaults, and every flag is overridable: kwonly=True is what actually prevents swaps at construction: BillingPeriod(start=a, end=b), never BillingPeriod(a, b). Don't override it without a reason. slots=True blocks functools.cachedproperty and ad-hoc attributes; override with slots=False rather than dropping @frozen. A bare @dataclass with no explicit frozen= fails the ratchet in posthog/test/repoinvariants/testdataclassdefaults.py and is flagged by the advisory prefer-frozen-dataclasses semgrep rule. @dataclass(frozen=False) passes; the ratchet asks for a stated choice, not immutability. If you only moved an existing bare @dataclass, regenerate the baseline with python posthog/test/repoinvariants/testdataclassdefaults.py instead of decorating it. Don't add frozen=False to a bare @dataclass you didn't otherwise touch. …

How to use it

Reference it in AdaL, Claude Code, Cursor or any coding agent — nothing to install:

@skills posthog/writing-dataclasses

View the source on GitHub

Browse the @skills marketplace