kodit 0.4.3__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kodit might be problematic. Click here for more details.
- kodit/_version.py +2 -2
- kodit/app.py +51 -23
- kodit/application/factories/reporting_factory.py +6 -2
- kodit/application/factories/server_factory.py +353 -0
- kodit/application/services/code_search_application_service.py +144 -0
- kodit/application/services/commit_indexing_application_service.py +700 -0
- kodit/application/services/indexing_worker_service.py +13 -44
- kodit/application/services/queue_service.py +24 -3
- kodit/application/services/reporting.py +0 -2
- kodit/application/services/sync_scheduler.py +15 -31
- kodit/cli.py +2 -753
- kodit/cli_utils.py +2 -9
- kodit/config.py +4 -97
- kodit/database.py +38 -1
- kodit/domain/enrichments/__init__.py +1 -0
- kodit/domain/enrichments/architecture/__init__.py +1 -0
- kodit/domain/enrichments/architecture/architecture.py +20 -0
- kodit/domain/enrichments/architecture/physical/__init__.py +1 -0
- kodit/domain/enrichments/architecture/physical/discovery_notes.py +14 -0
- kodit/domain/enrichments/architecture/physical/formatter.py +11 -0
- kodit/domain/enrichments/architecture/physical/physical.py +17 -0
- kodit/domain/enrichments/development/__init__.py +1 -0
- kodit/domain/enrichments/development/development.py +18 -0
- kodit/domain/enrichments/development/snippet/__init__.py +1 -0
- kodit/domain/enrichments/development/snippet/snippet.py +21 -0
- kodit/domain/enrichments/enricher.py +17 -0
- kodit/domain/enrichments/enrichment.py +39 -0
- kodit/domain/enrichments/request.py +12 -0
- kodit/domain/enrichments/response.py +11 -0
- kodit/domain/enrichments/usage/__init__.py +1 -0
- kodit/domain/enrichments/usage/api_docs.py +19 -0
- kodit/domain/enrichments/usage/usage.py +18 -0
- kodit/domain/{entities.py → entities/__init__.py} +50 -195
- kodit/domain/entities/git.py +190 -0
- kodit/domain/factories/__init__.py +1 -0
- kodit/domain/factories/git_repo_factory.py +76 -0
- kodit/domain/protocols.py +264 -64
- kodit/domain/services/bm25_service.py +5 -1
- kodit/domain/services/embedding_service.py +3 -0
- kodit/domain/services/enrichment_service.py +9 -30
- kodit/domain/services/git_repository_service.py +429 -0
- kodit/domain/services/git_service.py +300 -0
- kodit/domain/services/physical_architecture_service.py +182 -0
- kodit/domain/services/task_status_query_service.py +2 -2
- kodit/domain/value_objects.py +87 -135
- kodit/infrastructure/api/client/__init__.py +0 -2
- kodit/infrastructure/api/v1/__init__.py +0 -4
- kodit/infrastructure/api/v1/dependencies.py +92 -46
- kodit/infrastructure/api/v1/routers/__init__.py +0 -6
- kodit/infrastructure/api/v1/routers/commits.py +352 -0
- kodit/infrastructure/api/v1/routers/queue.py +2 -2
- kodit/infrastructure/api/v1/routers/repositories.py +282 -0
- kodit/infrastructure/api/v1/routers/search.py +31 -14
- kodit/infrastructure/api/v1/schemas/__init__.py +0 -24
- kodit/infrastructure/api/v1/schemas/commit.py +96 -0
- kodit/infrastructure/api/v1/schemas/context.py +2 -0
- kodit/infrastructure/api/v1/schemas/enrichment.py +29 -0
- kodit/infrastructure/api/v1/schemas/repository.py +128 -0
- kodit/infrastructure/api/v1/schemas/search.py +12 -9
- kodit/infrastructure/api/v1/schemas/snippet.py +58 -0
- kodit/infrastructure/api/v1/schemas/tag.py +31 -0
- kodit/infrastructure/api/v1/schemas/task_status.py +2 -0
- kodit/infrastructure/bm25/local_bm25_repository.py +16 -4
- kodit/infrastructure/bm25/vectorchord_bm25_repository.py +68 -52
- kodit/infrastructure/cloning/git/git_python_adaptor.py +534 -0
- kodit/infrastructure/cloning/git/working_copy.py +1 -1
- kodit/infrastructure/embedding/embedding_factory.py +3 -2
- kodit/infrastructure/embedding/local_vector_search_repository.py +1 -1
- kodit/infrastructure/embedding/vectorchord_vector_search_repository.py +111 -84
- kodit/infrastructure/enricher/__init__.py +1 -0
- kodit/infrastructure/enricher/enricher_factory.py +53 -0
- kodit/infrastructure/{enrichment/litellm_enrichment_provider.py → enricher/litellm_enricher.py} +36 -56
- kodit/infrastructure/{enrichment/local_enrichment_provider.py → enricher/local_enricher.py} +19 -24
- kodit/infrastructure/enricher/null_enricher.py +36 -0
- kodit/infrastructure/indexing/fusion_service.py +1 -1
- kodit/infrastructure/mappers/enrichment_mapper.py +83 -0
- kodit/infrastructure/mappers/git_mapper.py +193 -0
- kodit/infrastructure/mappers/snippet_mapper.py +104 -0
- kodit/infrastructure/mappers/task_mapper.py +5 -44
- kodit/infrastructure/physical_architecture/__init__.py +1 -0
- kodit/infrastructure/physical_architecture/detectors/__init__.py +1 -0
- kodit/infrastructure/physical_architecture/detectors/docker_compose_detector.py +336 -0
- kodit/infrastructure/physical_architecture/formatters/__init__.py +1 -0
- kodit/infrastructure/physical_architecture/formatters/narrative_formatter.py +149 -0
- kodit/infrastructure/reporting/log_progress.py +8 -5
- kodit/infrastructure/reporting/telemetry_progress.py +21 -0
- kodit/infrastructure/slicing/api_doc_extractor.py +836 -0
- kodit/infrastructure/slicing/ast_analyzer.py +1128 -0
- kodit/infrastructure/slicing/slicer.py +87 -421
- kodit/infrastructure/sqlalchemy/embedding_repository.py +43 -23
- kodit/infrastructure/sqlalchemy/enrichment_v2_repository.py +118 -0
- kodit/infrastructure/sqlalchemy/entities.py +402 -158
- kodit/infrastructure/sqlalchemy/git_branch_repository.py +274 -0
- kodit/infrastructure/sqlalchemy/git_commit_repository.py +346 -0
- kodit/infrastructure/sqlalchemy/git_repository.py +262 -0
- kodit/infrastructure/sqlalchemy/git_tag_repository.py +268 -0
- kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +479 -0
- kodit/infrastructure/sqlalchemy/task_repository.py +29 -23
- kodit/infrastructure/sqlalchemy/task_status_repository.py +24 -12
- kodit/infrastructure/sqlalchemy/unit_of_work.py +10 -14
- kodit/mcp.py +12 -30
- kodit/migrations/env.py +1 -0
- kodit/migrations/versions/04b80f802e0c_foreign_key_review.py +100 -0
- kodit/migrations/versions/19f8c7faf8b9_add_generic_enrichment_type.py +260 -0
- kodit/migrations/versions/7f15f878c3a1_add_new_git_entities.py +690 -0
- kodit/migrations/versions/f9e5ef5e688f_add_git_commits_number.py +43 -0
- kodit/py.typed +0 -0
- kodit/utils/dump_config.py +361 -0
- kodit/utils/dump_openapi.py +6 -4
- kodit/utils/path_utils.py +29 -0
- {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/METADATA +3 -3
- kodit-0.5.1.dist-info/RECORD +168 -0
- kodit/application/factories/code_indexing_factory.py +0 -195
- kodit/application/services/auto_indexing_service.py +0 -99
- kodit/application/services/code_indexing_application_service.py +0 -410
- kodit/domain/services/index_query_service.py +0 -70
- kodit/domain/services/index_service.py +0 -269
- kodit/infrastructure/api/client/index_client.py +0 -57
- kodit/infrastructure/api/v1/routers/indexes.py +0 -164
- kodit/infrastructure/api/v1/schemas/index.py +0 -101
- kodit/infrastructure/bm25/bm25_factory.py +0 -28
- kodit/infrastructure/cloning/__init__.py +0 -1
- kodit/infrastructure/cloning/metadata.py +0 -98
- kodit/infrastructure/enrichment/__init__.py +0 -1
- kodit/infrastructure/enrichment/enrichment_factory.py +0 -52
- kodit/infrastructure/enrichment/null_enrichment_provider.py +0 -19
- kodit/infrastructure/mappers/index_mapper.py +0 -345
- kodit/infrastructure/reporting/tdqm_progress.py +0 -38
- kodit/infrastructure/slicing/language_detection_service.py +0 -18
- kodit/infrastructure/sqlalchemy/index_repository.py +0 -646
- kodit-0.4.3.dist-info/RECORD +0 -125
- /kodit/infrastructure/{enrichment → enricher}/utils.py +0 -0
- {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/WHEEL +0 -0
- {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/entry_points.txt +0 -0
- {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,17 +14,10 @@ class SqlAlchemyUnitOfWork:
|
|
|
14
14
|
self._session_factory = session_factory
|
|
15
15
|
self._session: AsyncSession | None = None
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
def session(self) -> AsyncSession:
|
|
19
|
-
"""Get the current session."""
|
|
20
|
-
if self._session is None:
|
|
21
|
-
raise RuntimeError("UnitOfWork must be used within async context")
|
|
22
|
-
return self._session
|
|
23
|
-
|
|
24
|
-
async def __aenter__(self) -> "SqlAlchemyUnitOfWork":
|
|
17
|
+
async def __aenter__(self) -> "AsyncSession":
|
|
25
18
|
"""Enter the unit of work context."""
|
|
26
19
|
self._session = self._session_factory()
|
|
27
|
-
return self
|
|
20
|
+
return self._session
|
|
28
21
|
|
|
29
22
|
async def __aexit__(
|
|
30
23
|
self,
|
|
@@ -34,11 +27,14 @@ class SqlAlchemyUnitOfWork:
|
|
|
34
27
|
) -> None:
|
|
35
28
|
"""Exit the unit of work context."""
|
|
36
29
|
if self._session:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
try:
|
|
31
|
+
if exc_type is None: # Only commit if no exception
|
|
32
|
+
await self._session.commit()
|
|
33
|
+
else:
|
|
34
|
+
await self._session.rollback()
|
|
35
|
+
finally:
|
|
36
|
+
await self._session.close()
|
|
37
|
+
self._session = None
|
|
42
38
|
|
|
43
39
|
async def commit(self) -> None:
|
|
44
40
|
"""Commit the current transaction."""
|
kodit/mcp.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""MCP server for kodit."""
|
|
2
2
|
|
|
3
|
-
from collections.abc import AsyncIterator
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
@@ -9,35 +9,27 @@ from typing import Annotated
|
|
|
9
9
|
import structlog
|
|
10
10
|
from fastmcp import Context, FastMCP
|
|
11
11
|
from pydantic import Field
|
|
12
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
13
12
|
|
|
14
13
|
from kodit._version import version
|
|
15
|
-
from kodit.application.factories.
|
|
16
|
-
|
|
17
|
-
)
|
|
18
|
-
from kodit.application.factories.reporting_factory import create_server_operation
|
|
14
|
+
from kodit.application.factories.server_factory import ServerFactory
|
|
15
|
+
from kodit.application.services.code_search_application_service import MultiSearchResult
|
|
19
16
|
from kodit.config import AppContext
|
|
20
17
|
from kodit.database import Database
|
|
21
18
|
from kodit.domain.value_objects import (
|
|
22
19
|
MultiSearchRequest,
|
|
23
|
-
MultiSearchResult,
|
|
24
20
|
SnippetSearchFilters,
|
|
25
21
|
)
|
|
26
|
-
from kodit.infrastructure.sqlalchemy.task_status_repository import (
|
|
27
|
-
create_task_status_repository,
|
|
28
|
-
)
|
|
29
22
|
|
|
30
23
|
# Global database connection for MCP server
|
|
31
24
|
_mcp_db: Database | None = None
|
|
25
|
+
_mcp_server_factory: ServerFactory | None = None
|
|
32
26
|
|
|
33
27
|
|
|
34
28
|
@dataclass
|
|
35
29
|
class MCPContext:
|
|
36
30
|
"""Context for the MCP server."""
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
session_factory: Callable[[], AsyncSession]
|
|
40
|
-
app_context: AppContext
|
|
32
|
+
server_factory: ServerFactory
|
|
41
33
|
|
|
42
34
|
|
|
43
35
|
@asynccontextmanager
|
|
@@ -55,16 +47,12 @@ async def mcp_lifespan(_: FastMCP) -> AsyncIterator[MCPContext]:
|
|
|
55
47
|
Since they don't provide a good way to handle global state, we must use a
|
|
56
48
|
global variable to store the database connection.
|
|
57
49
|
"""
|
|
58
|
-
global
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
session=session,
|
|
65
|
-
app_context=app_context,
|
|
66
|
-
session_factory=_mcp_db.session_factory,
|
|
67
|
-
)
|
|
50
|
+
global _mcp_server_factory # noqa: PLW0603
|
|
51
|
+
if _mcp_server_factory is None:
|
|
52
|
+
app_context = AppContext()
|
|
53
|
+
db = await app_context.get_db()
|
|
54
|
+
_mcp_server_factory = ServerFactory(app_context, db.session_factory)
|
|
55
|
+
yield MCPContext(_mcp_server_factory)
|
|
68
56
|
|
|
69
57
|
|
|
70
58
|
def create_mcp_server(name: str, instructions: str | None = None) -> FastMCP:
|
|
@@ -180,13 +168,7 @@ def register_mcp_tools(mcp_server: FastMCP) -> None:
|
|
|
180
168
|
mcp_context: MCPContext = ctx.request_context.lifespan_context
|
|
181
169
|
|
|
182
170
|
# Use the unified application service
|
|
183
|
-
service =
|
|
184
|
-
app_context=mcp_context.app_context,
|
|
185
|
-
session_factory=mcp_context.session_factory,
|
|
186
|
-
operation=create_server_operation(
|
|
187
|
-
create_task_status_repository(mcp_context.session_factory)
|
|
188
|
-
),
|
|
189
|
-
)
|
|
171
|
+
service = mcp_context.server_factory.code_search_application_service()
|
|
190
172
|
|
|
191
173
|
log.debug("Searching for snippets")
|
|
192
174
|
|
kodit/migrations/env.py
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ruff: noqa
|
|
2
|
+
"""foreign key review
|
|
3
|
+
|
|
4
|
+
Revision ID: 04b80f802e0c
|
|
5
|
+
Revises: 7f15f878c3a1
|
|
6
|
+
Create Date: 2025-09-22 11:21:43.432880
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Sequence, Union
|
|
11
|
+
|
|
12
|
+
from alembic import op
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "04b80f802e0c"
|
|
18
|
+
down_revision: Union[str, None] = "7f15f878c3a1"
|
|
19
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
20
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
"""Upgrade schema."""
|
|
25
|
+
# SQLite doesn't support complex constraint alterations, so we'll drop and recreate tables
|
|
26
|
+
|
|
27
|
+
# Drop and recreate commit_indexes table with commit_sha as primary key
|
|
28
|
+
op.drop_table("commit_indexes")
|
|
29
|
+
op.create_table(
|
|
30
|
+
"commit_indexes",
|
|
31
|
+
sa.Column("commit_sha", sa.String(64), nullable=False),
|
|
32
|
+
sa.Column("status", sa.String(255), nullable=False),
|
|
33
|
+
sa.Column("indexed_at", sa.DateTime(), nullable=True),
|
|
34
|
+
sa.Column("error_message", sa.UnicodeText(), nullable=True),
|
|
35
|
+
sa.Column("files_processed", sa.Integer(), nullable=False, default=0),
|
|
36
|
+
sa.Column("processing_time_seconds", sa.Float(), nullable=False, default=0.0),
|
|
37
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
38
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
39
|
+
sa.PrimaryKeyConstraint("commit_sha", name="pk_commit_indexes"),
|
|
40
|
+
)
|
|
41
|
+
op.create_index("ix_commit_indexes_status", "commit_indexes", ["status"])
|
|
42
|
+
|
|
43
|
+
# Drop and recreate git_tracking_branches table with proper constraints
|
|
44
|
+
op.drop_table("git_tracking_branches")
|
|
45
|
+
op.create_table(
|
|
46
|
+
"git_tracking_branches",
|
|
47
|
+
sa.Column("repo_id", sa.Integer(), nullable=False),
|
|
48
|
+
sa.Column("name", sa.String(255), nullable=False),
|
|
49
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
50
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
51
|
+
sa.ForeignKeyConstraint(
|
|
52
|
+
["repo_id"], ["git_repos.id"], name="fk_tracking_branch_repo"
|
|
53
|
+
),
|
|
54
|
+
sa.PrimaryKeyConstraint("repo_id", "name", name="pk_git_tracking_branches"),
|
|
55
|
+
)
|
|
56
|
+
op.create_index("ix_git_tracking_branches_name", "git_tracking_branches", ["name"])
|
|
57
|
+
op.create_index(
|
|
58
|
+
"ix_git_tracking_branches_repo_id", "git_tracking_branches", ["repo_id"]
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def downgrade() -> None:
|
|
63
|
+
"""Downgrade schema."""
|
|
64
|
+
# Recreate the original tables
|
|
65
|
+
|
|
66
|
+
# Recreate commit_indexes table with id-based primary key (original structure)
|
|
67
|
+
op.drop_table("commit_indexes")
|
|
68
|
+
op.create_table(
|
|
69
|
+
"commit_indexes",
|
|
70
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
71
|
+
sa.Column("commit_sha", sa.String(64), nullable=False),
|
|
72
|
+
sa.Column("status", sa.String(255), nullable=False),
|
|
73
|
+
sa.Column("indexed_at", sa.DateTime(), nullable=True),
|
|
74
|
+
sa.Column("error_message", sa.UnicodeText(), nullable=True),
|
|
75
|
+
sa.Column("files_processed", sa.Integer(), nullable=False, default=0),
|
|
76
|
+
sa.Column("processing_time_seconds", sa.Float(), nullable=False, default=0.0),
|
|
77
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
78
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
79
|
+
sa.PrimaryKeyConstraint("id"),
|
|
80
|
+
)
|
|
81
|
+
op.create_index("ix_commit_indexes_status", "commit_indexes", ["status"])
|
|
82
|
+
|
|
83
|
+
# Recreate git_tracking_branches table with original structure
|
|
84
|
+
op.drop_table("git_tracking_branches")
|
|
85
|
+
op.create_table(
|
|
86
|
+
"git_tracking_branches",
|
|
87
|
+
sa.Column("repo_id", sa.Integer(), nullable=False),
|
|
88
|
+
sa.Column("name", sa.String(255), nullable=False),
|
|
89
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
90
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
91
|
+
sa.ForeignKeyConstraint(
|
|
92
|
+
["repo_id", "name"], ["git_branches.repo_id", "git_branches.name"]
|
|
93
|
+
),
|
|
94
|
+
sa.PrimaryKeyConstraint("repo_id"),
|
|
95
|
+
sa.UniqueConstraint("repo_id", "name", name="uix_repo_tracking_branch"),
|
|
96
|
+
)
|
|
97
|
+
op.create_index("ix_git_tracking_branches_name", "git_tracking_branches", ["name"])
|
|
98
|
+
op.create_index(
|
|
99
|
+
"ix_git_tracking_branches_repo_id", "git_tracking_branches", ["repo_id"]
|
|
100
|
+
)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# ruff: noqa
|
|
2
|
+
"""add generic enrichment type
|
|
3
|
+
|
|
4
|
+
Revision ID: 19f8c7faf8b9
|
|
5
|
+
Revises: f9e5ef5e688f
|
|
6
|
+
Create Date: 2025-09-29 21:38:19.093821
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Sequence, Union
|
|
11
|
+
|
|
12
|
+
from alembic import op
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
|
|
15
|
+
from kodit.domain.enrichments.development.development import ENRICHMENT_TYPE_DEVELOPMENT
|
|
16
|
+
from kodit.domain.enrichments.development.snippet.snippet import (
|
|
17
|
+
ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# revision identifiers, used by Alembic.
|
|
22
|
+
revision: str = "19f8c7faf8b9"
|
|
23
|
+
down_revision: Union[str, None] = "f9e5ef5e688f"
|
|
24
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
25
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def upgrade() -> None:
|
|
29
|
+
"""Upgrade schema."""
|
|
30
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
31
|
+
op.create_table(
|
|
32
|
+
"enrichments_v2",
|
|
33
|
+
sa.Column("content", sa.UnicodeText(), nullable=False),
|
|
34
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
35
|
+
sa.Column("type", sa.String(length=255), nullable=False),
|
|
36
|
+
sa.Column("subtype", sa.String(length=255), nullable=False),
|
|
37
|
+
sa.Column(
|
|
38
|
+
"created_at",
|
|
39
|
+
sa.DateTime(timezone=True),
|
|
40
|
+
nullable=False,
|
|
41
|
+
),
|
|
42
|
+
sa.Column(
|
|
43
|
+
"updated_at",
|
|
44
|
+
sa.DateTime(timezone=True),
|
|
45
|
+
nullable=False,
|
|
46
|
+
),
|
|
47
|
+
sa.PrimaryKeyConstraint("id"),
|
|
48
|
+
)
|
|
49
|
+
op.create_index(
|
|
50
|
+
"idx_type_subtype", "enrichments_v2", ["type", "subtype"], unique=False
|
|
51
|
+
)
|
|
52
|
+
op.create_index(
|
|
53
|
+
op.f("ix_enrichments_v2_type"), "enrichments_v2", ["type"], unique=False
|
|
54
|
+
)
|
|
55
|
+
op.create_index(
|
|
56
|
+
op.f("ix_enrichments_v2_subtype"), "enrichments_v2", ["subtype"], unique=False
|
|
57
|
+
)
|
|
58
|
+
op.create_table(
|
|
59
|
+
"enrichment_associations",
|
|
60
|
+
sa.Column("enrichment_id", sa.Integer(), nullable=False),
|
|
61
|
+
sa.Column("entity_type", sa.String(length=50), nullable=False),
|
|
62
|
+
sa.Column("entity_id", sa.String(length=255), nullable=False),
|
|
63
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
64
|
+
sa.Column(
|
|
65
|
+
"created_at",
|
|
66
|
+
sa.DateTime(timezone=True),
|
|
67
|
+
nullable=False,
|
|
68
|
+
),
|
|
69
|
+
sa.Column(
|
|
70
|
+
"updated_at",
|
|
71
|
+
sa.DateTime(timezone=True),
|
|
72
|
+
nullable=False,
|
|
73
|
+
),
|
|
74
|
+
sa.ForeignKeyConstraint(
|
|
75
|
+
["enrichment_id"], ["enrichments_v2.id"], ondelete="CASCADE"
|
|
76
|
+
),
|
|
77
|
+
sa.PrimaryKeyConstraint("id"),
|
|
78
|
+
sa.UniqueConstraint(
|
|
79
|
+
"entity_type", "entity_id", "enrichment_id", name="uix_entity_enrichment"
|
|
80
|
+
),
|
|
81
|
+
sqlite_autoincrement=True,
|
|
82
|
+
)
|
|
83
|
+
op.create_index(
|
|
84
|
+
"idx_entity_lookup",
|
|
85
|
+
"enrichment_associations",
|
|
86
|
+
["entity_type", "entity_id"],
|
|
87
|
+
unique=False,
|
|
88
|
+
)
|
|
89
|
+
op.create_index(
|
|
90
|
+
op.f("ix_enrichment_associations_enrichment_id"),
|
|
91
|
+
"enrichment_associations",
|
|
92
|
+
["enrichment_id"],
|
|
93
|
+
unique=False,
|
|
94
|
+
)
|
|
95
|
+
op.create_index(
|
|
96
|
+
op.f("ix_enrichment_associations_entity_id"),
|
|
97
|
+
"enrichment_associations",
|
|
98
|
+
["entity_id"],
|
|
99
|
+
unique=False,
|
|
100
|
+
)
|
|
101
|
+
op.create_index(
|
|
102
|
+
op.f("ix_enrichment_associations_entity_type"),
|
|
103
|
+
"enrichment_associations",
|
|
104
|
+
["entity_type"],
|
|
105
|
+
unique=False,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Migrate existing enrichments from old table to new structure
|
|
109
|
+
# All old enrichments are snippet enrichments
|
|
110
|
+
connection = op.get_bind()
|
|
111
|
+
|
|
112
|
+
# Check if old enrichments table exists
|
|
113
|
+
inspector = sa.inspect(connection)
|
|
114
|
+
if "enrichments" in inspector.get_table_names():
|
|
115
|
+
# Copy enrichments to new table
|
|
116
|
+
old_enrichments = connection.execute(
|
|
117
|
+
sa.text(
|
|
118
|
+
"SELECT id, content, created_at, updated_at, snippet_sha FROM enrichments"
|
|
119
|
+
)
|
|
120
|
+
).fetchall()
|
|
121
|
+
|
|
122
|
+
for old_enrichment in old_enrichments:
|
|
123
|
+
# Check if database supports RETURNING (PostgreSQL) or use lastrowid (SQLite)
|
|
124
|
+
dialect_name = connection.dialect.name
|
|
125
|
+
|
|
126
|
+
if dialect_name == "postgresql":
|
|
127
|
+
result = connection.execute(
|
|
128
|
+
sa.text(
|
|
129
|
+
"INSERT INTO enrichments_v2 (type, subtype, content, created_at, updated_at) "
|
|
130
|
+
"VALUES (:type, :subtype, :content, :created_at, :updated_at) "
|
|
131
|
+
"RETURNING id"
|
|
132
|
+
),
|
|
133
|
+
{
|
|
134
|
+
"type": ENRICHMENT_TYPE_DEVELOPMENT,
|
|
135
|
+
"subtype": ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY,
|
|
136
|
+
"content": old_enrichment[1],
|
|
137
|
+
"created_at": old_enrichment[2],
|
|
138
|
+
"updated_at": old_enrichment[3],
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
row = result.fetchone()
|
|
142
|
+
if row is None:
|
|
143
|
+
raise RuntimeError("Failed to insert enrichment")
|
|
144
|
+
new_enrichment_id = row[0]
|
|
145
|
+
else:
|
|
146
|
+
# SQLite and other databases
|
|
147
|
+
result = connection.execute(
|
|
148
|
+
sa.text(
|
|
149
|
+
"INSERT INTO enrichments_v2 (type, subtype, content, created_at, updated_at) "
|
|
150
|
+
"VALUES (:type, :subtype, :content, :created_at, :updated_at)"
|
|
151
|
+
),
|
|
152
|
+
{
|
|
153
|
+
"type": ENRICHMENT_TYPE_DEVELOPMENT,
|
|
154
|
+
"subtype": ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY,
|
|
155
|
+
"content": old_enrichment[1],
|
|
156
|
+
"created_at": old_enrichment[2],
|
|
157
|
+
"updated_at": old_enrichment[3],
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
new_enrichment_id = result.lastrowid
|
|
161
|
+
|
|
162
|
+
# Insert association (snippet_v2 is the entity type for snippets)
|
|
163
|
+
connection.execute(
|
|
164
|
+
sa.text(
|
|
165
|
+
"INSERT INTO enrichment_associations "
|
|
166
|
+
"(enrichment_id, entity_type, entity_id, created_at, updated_at) "
|
|
167
|
+
"VALUES (:enrichment_id, :entity_type, :entity_id, :created_at, :updated_at)"
|
|
168
|
+
),
|
|
169
|
+
{
|
|
170
|
+
"enrichment_id": new_enrichment_id,
|
|
171
|
+
"entity_type": "snippet_v2",
|
|
172
|
+
"entity_id": old_enrichment[4], # snippet_sha
|
|
173
|
+
"created_at": old_enrichment[2],
|
|
174
|
+
"updated_at": old_enrichment[3],
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Drop old enrichments table
|
|
179
|
+
op.drop_table("enrichments")
|
|
180
|
+
|
|
181
|
+
# ### end Alembic commands ###
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def downgrade() -> None:
|
|
185
|
+
"""Downgrade schema."""
|
|
186
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
187
|
+
# Recreate old enrichments table
|
|
188
|
+
op.create_table(
|
|
189
|
+
"enrichments",
|
|
190
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
191
|
+
sa.Column(
|
|
192
|
+
"created_at",
|
|
193
|
+
sa.DateTime(timezone=True),
|
|
194
|
+
nullable=False,
|
|
195
|
+
),
|
|
196
|
+
sa.Column(
|
|
197
|
+
"updated_at",
|
|
198
|
+
sa.DateTime(timezone=True),
|
|
199
|
+
nullable=False,
|
|
200
|
+
),
|
|
201
|
+
sa.Column("snippet_sha", sa.String(length=64), nullable=False),
|
|
202
|
+
sa.Column("type", sa.String(length=50), nullable=False),
|
|
203
|
+
sa.Column("content", sa.UnicodeText(), nullable=False),
|
|
204
|
+
sa.PrimaryKeyConstraint("id"),
|
|
205
|
+
sa.ForeignKeyConstraint(["snippet_sha"], ["snippets_v2.sha"]),
|
|
206
|
+
)
|
|
207
|
+
op.create_index(
|
|
208
|
+
op.f("ix_enrichments_snippet_sha"), "enrichments", ["snippet_sha"], unique=False
|
|
209
|
+
)
|
|
210
|
+
op.create_index(op.f("ix_enrichments_type"), "enrichments", ["type"], unique=False)
|
|
211
|
+
|
|
212
|
+
# Migrate data back from new structure to old table
|
|
213
|
+
connection = op.get_bind()
|
|
214
|
+
|
|
215
|
+
# Get enrichments with their associations
|
|
216
|
+
enrichments_with_associations = connection.execute(
|
|
217
|
+
sa.text(
|
|
218
|
+
"SELECT e.id, e.content, e.created_at, e.updated_at, "
|
|
219
|
+
"a.entity_id, a.entity_type "
|
|
220
|
+
"FROM enrichments_v2 e "
|
|
221
|
+
"JOIN enrichment_associations a ON e.id = a.enrichment_id "
|
|
222
|
+
"WHERE a.entity_type = 'snippet_v2'"
|
|
223
|
+
)
|
|
224
|
+
).fetchall()
|
|
225
|
+
|
|
226
|
+
for enrichment in enrichments_with_associations:
|
|
227
|
+
connection.execute(
|
|
228
|
+
sa.text(
|
|
229
|
+
"INSERT INTO enrichments "
|
|
230
|
+
"(content, created_at, updated_at, snippet_sha, type) "
|
|
231
|
+
"VALUES (:content, :created_at, :updated_at, :snippet_sha, :type)"
|
|
232
|
+
),
|
|
233
|
+
{
|
|
234
|
+
"content": enrichment[1],
|
|
235
|
+
"created_at": enrichment[2],
|
|
236
|
+
"updated_at": enrichment[3],
|
|
237
|
+
"snippet_sha": enrichment[4],
|
|
238
|
+
"type": "summarization", # Default to summarization type
|
|
239
|
+
},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
op.drop_index(
|
|
243
|
+
op.f("ix_enrichment_associations_entity_type"),
|
|
244
|
+
table_name="enrichment_associations",
|
|
245
|
+
)
|
|
246
|
+
op.drop_index(
|
|
247
|
+
op.f("ix_enrichment_associations_entity_id"),
|
|
248
|
+
table_name="enrichment_associations",
|
|
249
|
+
)
|
|
250
|
+
op.drop_index(
|
|
251
|
+
op.f("ix_enrichment_associations_enrichment_id"),
|
|
252
|
+
table_name="enrichment_associations",
|
|
253
|
+
)
|
|
254
|
+
op.drop_index("idx_entity_lookup", table_name="enrichment_associations")
|
|
255
|
+
op.drop_table("enrichment_associations")
|
|
256
|
+
op.drop_index(op.f("ix_enrichments_v2_subtype"), table_name="enrichments_v2")
|
|
257
|
+
op.drop_index(op.f("ix_enrichments_v2_type"), table_name="enrichments_v2")
|
|
258
|
+
op.drop_index("idx_type_subtype", table_name="enrichments_v2")
|
|
259
|
+
op.drop_table("enrichments_v2")
|
|
260
|
+
# ### end Alembic commands ###
|