writing-dataclasses

Enforces PostHog conventions for writing frozen Python dataclasses instead of tuples or dicts.

713|118|Updated Aug 11, 2020
One-click install
npx skills add https://github.com/PostHog/posthog-foss --skill writing-dataclasses
Or copy as Structured Prompt for Agent
Please help me install this Agent Skill.
Skill: writing-dataclasses
Source: https://github.com/PostHog/posthog-foss/tree/main/.agents/skills/writing-dataclasses
Command: npx skills add https://github.com/PostHog/posthog-foss --skill writing-dataclasses

SYSTEM DOCUMENTATION & REQUIREMENTS

What problem does it solve?

Positional tuples and dict[str, Any] payloads let same-typed values be swapped silently, causing runtime bugs that typecheckers miss. This Skill provides PostHog's house rules for replacing them with named, frozen dataclasses so swaps become typecheck failures.

Core Features & Use Cases

  • Decorator selection: Guides use of @frozen from posthog.dataclasses (frozen=True, kw_only=True, slots=True defaults) and when to override each flag.
  • Design rules: Covers naming, post_init invariant enforcement, Literal/enum field typing, dataclasses.replace evolution, match/case dispatch, and field(repr=False) for secrets.
  • Layering guidance: Explains when functions should accept a dataclass instead of unpacked fields, with carve-outs for Temporal/celery wire signatures and facade contract DTOs.
  • Use Case: When refactoring a function that returns (start, end) as a tuple, apply this Skill to convert it into a frozen BillingPeriod dataclass with keyword-only construction and invariant checks.

Quick Start

Ask the AI to review this Python function that returns a tuple of same-typed values and refactor it into a frozen dataclass following PostHog conventions.

Frequently Asked Questions about writing-dataclasses

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I write a frozen dataclass in the PostHog codebase?

Use @frozen from posthog.dataclasses, which applies frozen=True, kw_only=True, and slots=True by default. Construct instances with keyword arguments only, and override individual flags like @frozen(slots=False) only when needed, such as for functools.cached_property.

When should I use a dataclass instead of a tuple or dict in Python?

Use a dataclass when two or more returned elements share a type, when a tuple has roughly 3+ elements, or when a fixed set of values crosses a function boundary. Keep dicts for genuinely dynamic key sets and small tuples of clearly different types.

Why does a bare @dataclass fail PostHog's repo invariant test?

The blocking ratchet in posthog/test/repo_invariants/test_dataclass_defaults.py rejects new bare @dataclass decorators without an explicit frozen= choice. Adding frozen=True or frozen=False passes; the ratchet requires a stated choice, not immutability.

How do I keep secrets out of dataclass repr output?

Mark secret fields with field(repr=False) so they cannot leak through repr() into tracebacks and logs. Also avoid calling asdict() on such dataclasses in log output, since that bypasses the repr protection.

When should a function accept a dataclass instead of unpacked fields?

Accept the dataclass when the function's parameters mirror fields the caller already holds, following Fowler's Preserve Whole Object. Exceptions include invariant narrowing, Temporal or celery wire signatures, and facade contracts that double as HTTP request bodies.

Does this guidance apply to pydantic models or Django models?

No. The rules explicitly exclude pydantic models used as HogQL/query schema, DRF serializers, and Django models. They target internal value and result objects, plus facade contract DTOs which typically use pydantic.dataclasses.dataclass(frozen=True).