From d25c8d371f8a696de496ea44ecb02bbb24501d8d Mon Sep 17 00:00:00 2001 From: rayhpeng Date: Tue, 7 Apr 2026 10:32:40 +0800 Subject: [PATCH] refactor(persistence): organize entities into per-entity directories Restructure the persistence layer from horizontal "models/ + repositories/" split into vertical entity-aligned directories. Each entity (thread_meta, run, feedback) now owns its ORM model, abstract interface (where applicable), and concrete implementations under a single directory with an aggregating __init__.py for one-line imports. Layout: persistence/thread_meta/{base,model,sql,memory}.py persistence/run/{model,sql}.py persistence/feedback/{model,sql}.py models/__init__.py is kept as a facade so Alembic autogenerate continues to discover all ORM tables via Base.metadata. RunEventRow remains under models/run_event.py because its storage implementation lives in runtime/events/store/db.py and has no matching repository directory. The repositories/ directory is removed entirely. All call sites in gateway/deps.py and tests are updated to import from the new entity packages, e.g.: from deerflow.persistence.thread_meta import ThreadMetaRepository from deerflow.persistence.run import RunRepository from deerflow.persistence.feedback import FeedbackRepository Full test suite passes (1690 passed, 14 skipped). Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/gateway/deps.py | 8 +++---- .../deerflow/persistence/feedback/__init__.py | 6 ++++++ .../{models/feedback.py => feedback/model.py} | 0 .../feedback_repo.py => feedback/sql.py} | 2 +- .../deerflow/persistence/models/__init__.py | 21 ++++++++++++++++--- .../persistence/repositories/__init__.py | 5 ----- .../deerflow/persistence/run/__init__.py | 6 ++++++ .../{models/run.py => run/model.py} | 0 .../{repositories/run_repo.py => run/sql.py} | 2 +- .../persistence/thread_meta/__init__.py | 13 ++++++++++++ .../base.py} | 0 .../memory.py} | 2 +- .../thread_meta.py => thread_meta/model.py} | 0 .../sql.py} | 4 ++-- backend/tests/test_feedback.py | 2 +- backend/tests/test_run_journal.py | 2 +- backend/tests/test_run_repository.py | 2 +- backend/tests/test_thread_meta_repo.py | 2 +- 18 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 backend/packages/harness/deerflow/persistence/feedback/__init__.py rename backend/packages/harness/deerflow/persistence/{models/feedback.py => feedback/model.py} (100%) rename backend/packages/harness/deerflow/persistence/{repositories/feedback_repo.py => feedback/sql.py} (98%) delete mode 100644 backend/packages/harness/deerflow/persistence/repositories/__init__.py create mode 100644 backend/packages/harness/deerflow/persistence/run/__init__.py rename backend/packages/harness/deerflow/persistence/{models/run.py => run/model.py} (100%) rename backend/packages/harness/deerflow/persistence/{repositories/run_repo.py => run/sql.py} (99%) create mode 100644 backend/packages/harness/deerflow/persistence/thread_meta/__init__.py rename backend/packages/harness/deerflow/persistence/{repositories/thread_meta_base.py => thread_meta/base.py} (100%) rename backend/packages/harness/deerflow/persistence/{repositories/thread_meta_memory.py => thread_meta/memory.py} (97%) rename backend/packages/harness/deerflow/persistence/{models/thread_meta.py => thread_meta/model.py} (100%) rename backend/packages/harness/deerflow/persistence/{repositories/thread_meta_repo.py => thread_meta/sql.py} (97%) diff --git a/backend/app/gateway/deps.py b/backend/app/gateway/deps.py index c6eb18a71..bdcea365c 100644 --- a/backend/app/gateway/deps.py +++ b/backend/app/gateway/deps.py @@ -46,15 +46,15 @@ async def langgraph_runtime(app: FastAPI) -> AsyncGenerator[None, None]: # Initialize repositories — one get_session_factory() call for all. sf = get_session_factory() if sf is not None: - from deerflow.persistence.repositories.feedback_repo import FeedbackRepository - from deerflow.persistence.repositories.run_repo import RunRepository - from deerflow.persistence.repositories.thread_meta_repo import ThreadMetaRepository + from deerflow.persistence.feedback import FeedbackRepository + from deerflow.persistence.run import RunRepository + from deerflow.persistence.thread_meta import ThreadMetaRepository app.state.run_store = RunRepository(sf) app.state.feedback_repo = FeedbackRepository(sf) app.state.thread_meta_repo = ThreadMetaRepository(sf) else: - from deerflow.persistence.repositories.thread_meta_memory import MemoryThreadMetaStore + from deerflow.persistence.thread_meta import MemoryThreadMetaStore from deerflow.runtime.runs.store.memory import MemoryRunStore app.state.run_store = MemoryRunStore() diff --git a/backend/packages/harness/deerflow/persistence/feedback/__init__.py b/backend/packages/harness/deerflow/persistence/feedback/__init__.py new file mode 100644 index 000000000..ee958b027 --- /dev/null +++ b/backend/packages/harness/deerflow/persistence/feedback/__init__.py @@ -0,0 +1,6 @@ +"""Feedback persistence — ORM and SQL repository.""" + +from deerflow.persistence.feedback.model import FeedbackRow +from deerflow.persistence.feedback.sql import FeedbackRepository + +__all__ = ["FeedbackRepository", "FeedbackRow"] diff --git a/backend/packages/harness/deerflow/persistence/models/feedback.py b/backend/packages/harness/deerflow/persistence/feedback/model.py similarity index 100% rename from backend/packages/harness/deerflow/persistence/models/feedback.py rename to backend/packages/harness/deerflow/persistence/feedback/model.py diff --git a/backend/packages/harness/deerflow/persistence/repositories/feedback_repo.py b/backend/packages/harness/deerflow/persistence/feedback/sql.py similarity index 98% rename from backend/packages/harness/deerflow/persistence/repositories/feedback_repo.py rename to backend/packages/harness/deerflow/persistence/feedback/sql.py index f9315488c..eae2f9997 100644 --- a/backend/packages/harness/deerflow/persistence/repositories/feedback_repo.py +++ b/backend/packages/harness/deerflow/persistence/feedback/sql.py @@ -11,7 +11,7 @@ from datetime import UTC, datetime from sqlalchemy import case, func, select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker -from deerflow.persistence.models.feedback import FeedbackRow +from deerflow.persistence.feedback.model import FeedbackRow class FeedbackRepository: diff --git a/backend/packages/harness/deerflow/persistence/models/__init__.py b/backend/packages/harness/deerflow/persistence/models/__init__.py index fa4d0d1b2..659ac07f9 100644 --- a/backend/packages/harness/deerflow/persistence/models/__init__.py +++ b/backend/packages/harness/deerflow/persistence/models/__init__.py @@ -1,6 +1,21 @@ -from deerflow.persistence.models.feedback import FeedbackRow -from deerflow.persistence.models.run import RunRow +"""ORM model registration entry point. + +Importing this module ensures all ORM models are registered with +``Base.metadata`` so Alembic autogenerate detects every table. + +The actual ORM classes have moved to entity-specific subpackages: +- ``deerflow.persistence.thread_meta`` +- ``deerflow.persistence.run`` +- ``deerflow.persistence.feedback`` + +``RunEventRow`` remains in ``deerflow.persistence.models.run_event`` because +its storage implementation lives in ``deerflow.runtime.events.store.db`` and +there is no matching entity directory. +""" + +from deerflow.persistence.feedback.model import FeedbackRow from deerflow.persistence.models.run_event import RunEventRow -from deerflow.persistence.models.thread_meta import ThreadMetaRow +from deerflow.persistence.run.model import RunRow +from deerflow.persistence.thread_meta.model import ThreadMetaRow __all__ = ["FeedbackRow", "RunEventRow", "RunRow", "ThreadMetaRow"] diff --git a/backend/packages/harness/deerflow/persistence/repositories/__init__.py b/backend/packages/harness/deerflow/persistence/repositories/__init__.py deleted file mode 100644 index 0b9f76afa..000000000 --- a/backend/packages/harness/deerflow/persistence/repositories/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from deerflow.persistence.repositories.feedback_repo import FeedbackRepository -from deerflow.persistence.repositories.run_repo import RunRepository -from deerflow.persistence.repositories.thread_meta_repo import ThreadMetaRepository - -__all__ = ["FeedbackRepository", "RunRepository", "ThreadMetaRepository"] diff --git a/backend/packages/harness/deerflow/persistence/run/__init__.py b/backend/packages/harness/deerflow/persistence/run/__init__.py new file mode 100644 index 000000000..0aa01e7ea --- /dev/null +++ b/backend/packages/harness/deerflow/persistence/run/__init__.py @@ -0,0 +1,6 @@ +"""Run metadata persistence — ORM and SQL repository.""" + +from deerflow.persistence.run.model import RunRow +from deerflow.persistence.run.sql import RunRepository + +__all__ = ["RunRepository", "RunRow"] diff --git a/backend/packages/harness/deerflow/persistence/models/run.py b/backend/packages/harness/deerflow/persistence/run/model.py similarity index 100% rename from backend/packages/harness/deerflow/persistence/models/run.py rename to backend/packages/harness/deerflow/persistence/run/model.py diff --git a/backend/packages/harness/deerflow/persistence/repositories/run_repo.py b/backend/packages/harness/deerflow/persistence/run/sql.py similarity index 99% rename from backend/packages/harness/deerflow/persistence/repositories/run_repo.py rename to backend/packages/harness/deerflow/persistence/run/sql.py index aeff07c11..fac88d968 100644 --- a/backend/packages/harness/deerflow/persistence/repositories/run_repo.py +++ b/backend/packages/harness/deerflow/persistence/run/sql.py @@ -14,7 +14,7 @@ from typing import Any from sqlalchemy import func, select, update from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker -from deerflow.persistence.models.run import RunRow +from deerflow.persistence.run.model import RunRow from deerflow.runtime.runs.store.base import RunStore diff --git a/backend/packages/harness/deerflow/persistence/thread_meta/__init__.py b/backend/packages/harness/deerflow/persistence/thread_meta/__init__.py new file mode 100644 index 000000000..8e497bb7e --- /dev/null +++ b/backend/packages/harness/deerflow/persistence/thread_meta/__init__.py @@ -0,0 +1,13 @@ +"""Thread metadata persistence — ORM, abstract store, and concrete implementations.""" + +from deerflow.persistence.thread_meta.base import ThreadMetaStore +from deerflow.persistence.thread_meta.memory import MemoryThreadMetaStore +from deerflow.persistence.thread_meta.model import ThreadMetaRow +from deerflow.persistence.thread_meta.sql import ThreadMetaRepository + +__all__ = [ + "MemoryThreadMetaStore", + "ThreadMetaRepository", + "ThreadMetaRow", + "ThreadMetaStore", +] diff --git a/backend/packages/harness/deerflow/persistence/repositories/thread_meta_base.py b/backend/packages/harness/deerflow/persistence/thread_meta/base.py similarity index 100% rename from backend/packages/harness/deerflow/persistence/repositories/thread_meta_base.py rename to backend/packages/harness/deerflow/persistence/thread_meta/base.py diff --git a/backend/packages/harness/deerflow/persistence/repositories/thread_meta_memory.py b/backend/packages/harness/deerflow/persistence/thread_meta/memory.py similarity index 97% rename from backend/packages/harness/deerflow/persistence/repositories/thread_meta_memory.py rename to backend/packages/harness/deerflow/persistence/thread_meta/memory.py index b0e87165d..228e36356 100644 --- a/backend/packages/harness/deerflow/persistence/repositories/thread_meta_memory.py +++ b/backend/packages/harness/deerflow/persistence/thread_meta/memory.py @@ -12,7 +12,7 @@ from typing import Any from langgraph.store.base import BaseStore -from deerflow.persistence.repositories.thread_meta_base import ThreadMetaStore +from deerflow.persistence.thread_meta.base import ThreadMetaStore THREADS_NS: tuple[str, ...] = ("threads",) diff --git a/backend/packages/harness/deerflow/persistence/models/thread_meta.py b/backend/packages/harness/deerflow/persistence/thread_meta/model.py similarity index 100% rename from backend/packages/harness/deerflow/persistence/models/thread_meta.py rename to backend/packages/harness/deerflow/persistence/thread_meta/model.py diff --git a/backend/packages/harness/deerflow/persistence/repositories/thread_meta_repo.py b/backend/packages/harness/deerflow/persistence/thread_meta/sql.py similarity index 97% rename from backend/packages/harness/deerflow/persistence/repositories/thread_meta_repo.py rename to backend/packages/harness/deerflow/persistence/thread_meta/sql.py index c4a839c77..fa34f0892 100644 --- a/backend/packages/harness/deerflow/persistence/repositories/thread_meta_repo.py +++ b/backend/packages/harness/deerflow/persistence/thread_meta/sql.py @@ -8,8 +8,8 @@ from typing import Any from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker -from deerflow.persistence.models.thread_meta import ThreadMetaRow -from deerflow.persistence.repositories.thread_meta_base import ThreadMetaStore +from deerflow.persistence.thread_meta.base import ThreadMetaStore +from deerflow.persistence.thread_meta.model import ThreadMetaRow class ThreadMetaRepository(ThreadMetaStore): diff --git a/backend/tests/test_feedback.py b/backend/tests/test_feedback.py index 67edecafe..ed6c09f44 100644 --- a/backend/tests/test_feedback.py +++ b/backend/tests/test_feedback.py @@ -5,7 +5,7 @@ Uses temp SQLite DB for ORM tests. import pytest -from deerflow.persistence.repositories.feedback_repo import FeedbackRepository +from deerflow.persistence.feedback import FeedbackRepository async def _make_feedback_repo(tmp_path): diff --git a/backend/tests/test_run_journal.py b/backend/tests/test_run_journal.py index 314a1c270..dbb307a55 100644 --- a/backend/tests/test_run_journal.py +++ b/backend/tests/test_run_journal.py @@ -373,7 +373,7 @@ class TestDbBackedLifecycle: async def test_full_lifecycle_with_sqlite(self, tmp_path): """Full lifecycle with SQLite-backed RunRepository + DbRunEventStore.""" from deerflow.persistence.engine import close_engine, get_session_factory, init_engine - from deerflow.persistence.repositories.run_repo import RunRepository + from deerflow.persistence.run import RunRepository from deerflow.runtime.events.store.db import DbRunEventStore from deerflow.runtime.runs.manager import RunManager diff --git a/backend/tests/test_run_repository.py b/backend/tests/test_run_repository.py index c1ecabc99..0a3ddc7dc 100644 --- a/backend/tests/test_run_repository.py +++ b/backend/tests/test_run_repository.py @@ -5,7 +5,7 @@ Uses a temp SQLite DB to test ORM-backed CRUD operations. import pytest -from deerflow.persistence.repositories.run_repo import RunRepository +from deerflow.persistence.run import RunRepository async def _make_repo(tmp_path): diff --git a/backend/tests/test_thread_meta_repo.py b/backend/tests/test_thread_meta_repo.py index 9104275ff..6d60862bc 100644 --- a/backend/tests/test_thread_meta_repo.py +++ b/backend/tests/test_thread_meta_repo.py @@ -2,7 +2,7 @@ import pytest -from deerflow.persistence.repositories.thread_meta_repo import ThreadMetaRepository +from deerflow.persistence.thread_meta import ThreadMetaRepository async def _make_repo(tmp_path):