Skip to content

Extract shared from_dict base/decorator across 5 model classes #70

@bradjin8

Description

@bradjin8

Repository: cppa-cursor-browser
Assignee: Brad @bradjin8
Points: 3
Severity: Medium

Problem

Five model classes (Workspace, Composer, WorkspaceLocalComposer, Bubble, CliSessionMeta) each implement a from_dict class method with identical structure: check isinstance(raw, dict), validate required fields, check types, raise SchemaError on failure. The pattern is identical in shape but implemented independently in each file. This becomes structural when combined with the silent failure chain: each from_dict site is wrapped in its own try/except block at the call site, and the handling strategy varies by caller. A fix to exception handling must be applied at every call site independently, creating fresh opportunities for logged-vs-silent inconsistency.

Acceptance Criteria

  • A shared base class, mixin, or decorator centralizes the from_dict validation pattern (isinstance check, required field validation, type checking, SchemaError raising)
  • All 5 model classes use the shared mechanism instead of independent implementations
  • Model-specific field definitions remain in each model class (only the validation boilerplate is shared)
  • SchemaError messages retain the same level of detail (field name, expected type, actual type)
  • All existing model tests pass without modification
  • Tests pass in CI
  • PR approved by at least 1 reviewer

Implementation Notes

The cleanest approach is a @validated_from_dict decorator or a base class with a generic from_dict that accepts a field specification. For example, each model defines _REQUIRED_FIELDS = {"id": str, "title": str} and the shared from_dict validates against this spec before delegating to the model-specific constructor. Alternatively, a validate_raw(raw, required_fields, model_name) utility function can be called at the top of each from_dict, reducing but not eliminating the boilerplate. The frozen dataclass pattern already used in the models is compatible with either approach. The ExportEntry model in models/export.py follows the same pattern — include it.

References

  • Eval finding: Test 5 (Boilerplate Minimality) — part of the "Distributed Constructor Problem" compound (T5+T7+T11)
  • Related files: models/workspace.py, models/conversation.py, models/cli_session.py, models/export.py, models/bubble.py (or equivalent)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions