Skip to content

refactor: purge dict-compat, add Category dataclass (Issue #ARCH-24)#1009

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

refactor: purge dict-compat, add Category dataclass (Issue #ARCH-24)#1009
arockwell merged 10 commits intomainfrom
feature/data-model

Conversation

@arockwell
Copy link
Owner

Summary

  • Remove dict-compat layer from all 4 domain dataclasses (Document, SearchHit, Task, TaskLogEntry) — __getitem__, .get(), keys(), items(), values(), __contains__ all gone
  • Convert ~220 bracket access sites to attribute access across 26 files (commands, UI, services, tests)
  • Add Category dataclass (emdx/models/category.py) replacing CategoryDict and CategoryWithStatsDict TypedDicts
  • Remove 6 TypedDicts total: CategoryDict, CategoryWithStatsDict, TaskDict, EpicTaskDict, EpicViewDict, TaskLogEntryDict
  • Net -111 lines (655 added, 766 removed)

Test plan

  • ruff check — zero errors
  • ruff format — all formatted
  • mypy — no issues (pre-commit hook passed)
  • pytest tests/ -x -q — 2076 passed, 8 skipped, 0 failures
  • Verified no remaining imports of removed TypedDicts

🤖 Generated with Claude Code

arockwell and others added 9 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>
…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>
…e #ARCH-24)

Script-converted 102 mock return values from raw dicts to
Task.from_row()/TaskLogEntry.from_row() so attribute access works
on mock return values. Enables removing dict-compat layer later.

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

Remove __getitem__/.get()/keys()/items()/values()/__contains__ from
Document, SearchHit, Task, and TaskLogEntry dataclasses. Convert all
~220 bracket access sites across 26 files to attribute access.

Add Category dataclass replacing CategoryDict/CategoryWithStatsDict
TypedDicts. Wire into categories.py, commands, and tests.

Net -217 lines removed. All 2076 tests pass.

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arockwell arockwell enabled auto-merge (squash) March 7, 2026 07:38
…(Issue #ARCH-24)

serve.py used dict(doc)/dict(task) which fails without dict-compat.
core.py had one remaining dict(r) on search results.
Test mocks in test_serve.py, test_commands_core.py, and
test_v028_regressions.py still returned raw dicts instead of
SearchHit/Document/Task objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arockwell arockwell merged commit d4382f4 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