refactor: convert SessionSummary to frozen dataclass (#1067)#1069
refactor: convert SessionSummary to frozen dataclass (#1067)#1069
Conversation
…taclass
Convert SessionSummary to @dataclasses.dataclass(frozen=True, slots=True)
to align with the coding guideline: 'Internal intermediate state uses
frozen dataclasses with slots=True'.
Changes:
- models.py: Replace BaseModel with frozen dataclass, move model_validator
logic to __post_init__, use dataclasses.field for mutable defaults
- parser.py: Replace model_copy(update={...}) with dataclasses.replace()
- test_models.py: Update invariant tests to expect ValueError instead of
ValidationError, add FrozenInstanceError and dataclasses.replace tests
- test_parser.py: Replace model_dump() comparison with direct equality
- architecture.md: Update models.py description
Closes #1067
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Refactors copilot_usage’s internal session aggregation type by converting SessionSummary from a Pydantic BaseModel to a frozen, slotted stdlib dataclass, aligning internal-state modeling with the project’s “Pydantic at the boundary” direction while keeping invariant validation.
Changes:
- Converted
SessionSummaryto@dataclasses.dataclass(frozen=True, slots=True)and moved invariants to__post_init__. - Updated parser/cache update logic to use
dataclasses.replace(...)instead of Pydanticmodel_copy(update=...). - Adjusted tests and docs to reflect dataclass semantics (equality, exception types, frozen behavior).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/copilot_usage/models.py | Converts SessionSummary to a frozen dataclass and enforces invariants in __post_init__; updates mutable defaults to dataclasses.field(default_factory=...). |
| src/copilot_usage/parser.py | Updates cached-summary name refresh to use dataclasses.replace(...) for immutable SessionSummary. |
| tests/copilot_usage/test_models.py | Updates invariant tests to ValueError and adds coverage for frozen/dataclass replace behavior. |
| tests/copilot_usage/test_parser.py | Replaces model_dump()-based comparisons with direct dataclass equality for cached summaries. |
| src/copilot_usage/docs/architecture.md | Updates architecture docs to reflect SessionSummary as a frozen dataclass rather than a Pydantic model. |
| shutdown_cycles: list[tuple[datetime | None, SessionShutdownData]] = ( | ||
| dataclasses.field(default_factory=lambda: []) |
There was a problem hiding this comment.
shutdown_cycles uses dataclasses.field(default_factory=lambda: []). Prefer default_factory=list (or default_factory=list[tuple[...]] not possible) to avoid an unnecessary lambda and keep the field factory picklable/readable; also the extra parentheses around the field(...) call can be dropped for clarity.
| shutdown_cycles: list[tuple[datetime | None, SessionShutdownData]] = ( | |
| dataclasses.field(default_factory=lambda: []) | |
| shutdown_cycles: list[tuple[datetime | None, SessionShutdownData]] = dataclasses.field( | |
| default_factory=list |
Change default_factory=dict to default_factory=lambda: {} for the
model_metrics field on SessionSummary. Per coding guidelines,
dataclass fields must use lambda factories for mutable defaults to
avoid a known pyright strict-mode false-positive.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI FixRoot cause: Fix: Changed
Warning
|
|
❌ Pipeline orchestrator: CI still failing after ci-fixer attempt. Marking as stuck for human review. |
Closes #1067
Summary
Converts
SessionSummaryfrom a PydanticBaseModelto a@dataclasses.dataclass(frozen=True, slots=True)to align with the coding guideline: "Internal intermediate state uses frozendataclasseswithslots=True".SessionSummaryis internal intermediate state — populated bybuild_session_summaryfrom already-validatedSessionEventobjects, never parsed directly from JSON. Making it a frozen dataclass prevents accidental field mutation on cached instances.Changes
src/copilot_usage/models.pyBaseModelwith frozen dataclass; move@model_validatorto__post_init__; usedataclasses.fieldfor mutable defaults; remove unusedSelfandmodel_validatorimportssrc/copilot_usage/parser.pysummary.model_copy(update={...})withdataclasses.replace(summary, ...)tests/copilot_usage/test_models.pyValueError(notValidationError); addTestSessionSummaryFrozenclass withFrozenInstanceError,__post_init__validation, anddataclasses.replaceteststests/copilot_usage/test_parser.pymodel_dump()comparison with direct dataclass equalitysrc/copilot_usage/docs/architecture.mdmodels.pydescription to reflect the changeTesting
TestSessionSummaryFrozenwith 3 tests:FrozenInstanceError__post_init__raisesValueErrorwhenactive_model_calls > model_callsdataclasses.replaceproduces a new instance with updated fieldsValueErrorexception typeWarning
The following domains were blocked by the firewall during workflow execution:
astral.shpypi.orgTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.