Skip to content

feat(models): Task dataclass replaces TypedDict cast zoo (Issue #ARCH-24)#1007

Merged
arockwell merged 6 commits intomainfrom
feature/data-model
Mar 7, 2026
Merged

feat(models): Task dataclass replaces TypedDict cast zoo (Issue #ARCH-24)#1007
arockwell merged 6 commits intomainfrom
feature/data-model

Conversation

@arockwell
Copy link
Owner

Summary

Phase 5 of the data model refactor (ARCH-24). Introduces a Task dataclass that replaces the scattered TaskDict/EpicTaskDict/EpicViewDict/TaskLogEntryDict TypedDicts with proper domain model objects.

  • New emdx/models/task.py: Task and TaskLogEntry dataclasses with from_row() factory methods, datetime parsing at DB boundary, and dict-compat layer for incremental migration
  • models/tasks.py: All functions return Task/TaskLogEntry instead of cast(TypedDict, dict(row))
  • Consumer migration: commands/tasks.py, ui/task_view.py type annotations updated from TaskDictTask
  • Dead type cleanup: Removed TaskDict, EpicTaskDict, EpicViewDict, TaskLogEntryDict from models/types.py
  • Test updates: Test factories use Task.from_row(), fixed item assignment patterns

Same pattern as the Document dataclass (PR #1006): @dataclass(slots=True), __getitem__/.get() backward compat, to_dict() serialization.

Test plan

  • 2068 tests pass, 0 failures
  • ruff check clean
  • ruff format clean
  • mypy clean (4 files checked)
  • Pre-commit hooks pass

🤖 Generated with Claude Code

arockwell and others added 5 commits March 7, 2026 01:15
…o (Issue #ARCH-24)

Introduce a proper Document domain object (@DataClass) that replaces the
scattered TypedDict projections (DocumentRow, DocumentListItem,
RecentDocumentItem, DeletedDocumentItem, ChildDocumentItem,
SupersedeCandidate) with a single type constructed via factory methods.

Phase 1 — Document dataclass (emdx/models/document.py):
- @DataClass(slots=True) with all 15 document fields
- from_row() / from_partial_row() factories with datetime parsing
- __getitem__ / .get() / keys() / items() dict-compat layer
  so all 158 existing bracket-access sites keep working
- to_dict() with datetime→ISO serialization for JSON output
- 31 unit tests covering construction, parsing, compat, serialization

Phase 1b — SearchHit dataclass (emdx/models/search.py):
- Wraps Document + snippet + rank for search results
- Same dict-compat interface with field fallthrough

Phase 2 — Wire into database layer:
- database/documents.py: all functions return Document instead of
  TypedDict casts. Removed _parse_doc_datetimes() (absorbed into
  Document.from_row())
- database/search.py: returns list[SearchHit] instead of
  list[SearchResult]
- database/__init__.py: SQLiteDatabase methods updated to return
  Document / SearchHit
- commands/context.py: switched from DocumentRow to Document

25 cast() calls eliminated. 2068 tests passing, zero breakage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…CH-24)

Phase 3 of the data model refactor — migrate bracket access to attribute
access across 10 consumer files and 9 test files.

Converted files:
- commands/core.py (43 sites — display_save_result, view, edit, delete,
  _find_all, _find_recent, _view_review, _print_view_header_*)
- commands/context.py, history.py, tags.py, tasks.py, wiki.py, gist.py,
  briefing.py, trash.py
- ui/activity/activity_data.py

Updated test mocks to return Document objects instead of raw dicts:
- test_commands_core.py, test_commands_tags.py, test_commands_trash.py,
  test_gist.py, test_v028_regressions.py, test_activity_doc_type.py,
  test_activity_view.py, test_task_commands.py, test_view_review.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y (Issue #ARCH-24)

Phase 4 — remove DocumentRow, DocumentListItem, RecentDocumentItem,
DeletedDocumentItem, ChildDocumentItem, SupersedeCandidate, and
SearchResult from database/types.py. All replaced by the Document
dataclass and SearchHit in emdx/models/.

Remaining types in database/types.py are non-document structures:
MostViewedDoc, DatabaseStats, DocumentLinkDetail, WikiArticleTimingDict,
StandingQueryRow, StandingQueryMatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…(Issue #ARCH-24)

- Create emdx/models/task.py with Task and TaskLogEntry dataclasses
- Factory methods from_row()/from_partial_row() with datetime parsing
- Dict-compat layer (__getitem__, .get()) for incremental migration
- Wire into models/tasks.py: all functions return Task/TaskLogEntry
- Migrate consumers: commands/tasks.py, ui/task_view.py type annotations
- Fix mypy errors: switch 3 bracket accesses to attribute access
- Remove TaskDict, EpicTaskDict, EpicViewDict, TaskLogEntryDict from types.py
- Update test factories to use Task.from_row()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arockwell arockwell enabled auto-merge (squash) March 7, 2026 06:54
…at needed)

Tests mock get_task() with raw dicts, so attribute access like
task.epic_key breaks. Restore bracket access via dict-compat layer
until test mocks are migrated to Task objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arockwell arockwell merged commit 2cb4992 into main Mar 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant