kodit 0.2.9__py3-none-any.whl → 0.3.0__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 +36 -1
- kodit/cli.py +15 -0
- kodit/config.py +40 -3
- kodit/infrastructure/indexing/auto_indexing_service.py +84 -0
- {kodit-0.2.9.dist-info → kodit-0.3.0.dist-info}/METADATA +1 -1
- {kodit-0.2.9.dist-info → kodit-0.3.0.dist-info}/RECORD +10 -9
- {kodit-0.2.9.dist-info → kodit-0.3.0.dist-info}/WHEEL +0 -0
- {kodit-0.2.9.dist-info → kodit-0.3.0.dist-info}/entry_points.txt +0 -0
- {kodit-0.2.9.dist-info → kodit-0.3.0.dist-info}/licenses/LICENSE +0 -0
kodit/_version.py
CHANGED
kodit/app.py
CHANGED
|
@@ -1,14 +1,49 @@
|
|
|
1
1
|
"""FastAPI application for kodit API."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
|
|
3
6
|
from asgi_correlation_id import CorrelationIdMiddleware
|
|
4
7
|
from fastapi import FastAPI
|
|
5
8
|
|
|
9
|
+
from kodit.config import AppContext
|
|
10
|
+
from kodit.infrastructure.indexing.auto_indexing_service import AutoIndexingService
|
|
6
11
|
from kodit.mcp import mcp
|
|
7
12
|
from kodit.middleware import ASGICancelledErrorMiddleware, logging_middleware
|
|
8
13
|
|
|
14
|
+
# Global auto-indexing service
|
|
15
|
+
_auto_indexing_service: AutoIndexingService | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@asynccontextmanager
|
|
19
|
+
async def app_lifespan(_: FastAPI) -> AsyncIterator[None]:
|
|
20
|
+
"""Manage application lifespan for auto-indexing."""
|
|
21
|
+
global _auto_indexing_service # noqa: PLW0603
|
|
22
|
+
# Start auto-indexing service
|
|
23
|
+
app_context = AppContext()
|
|
24
|
+
db = await app_context.get_db()
|
|
25
|
+
_auto_indexing_service = AutoIndexingService(
|
|
26
|
+
app_context=app_context,
|
|
27
|
+
session_factory=db.session_factory,
|
|
28
|
+
)
|
|
29
|
+
await _auto_indexing_service.start_background_indexing()
|
|
30
|
+
yield
|
|
31
|
+
if _auto_indexing_service:
|
|
32
|
+
await _auto_indexing_service.stop()
|
|
33
|
+
|
|
34
|
+
|
|
9
35
|
# See https://gofastmcp.com/deployment/asgi#fastapi-integration
|
|
10
36
|
mcp_app = mcp.sse_app()
|
|
11
|
-
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@asynccontextmanager
|
|
40
|
+
async def combined_lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
41
|
+
"""Combine app and MCP lifespans."""
|
|
42
|
+
async with app_lifespan(app), mcp_app.router.lifespan_context(app):
|
|
43
|
+
yield
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
app = FastAPI(title="kodit API", lifespan=combined_lifespan)
|
|
12
47
|
|
|
13
48
|
# Add middleware
|
|
14
49
|
app.middleware("http")(logging_middleware)
|
kodit/cli.py
CHANGED
|
@@ -59,12 +59,17 @@ def cli(
|
|
|
59
59
|
|
|
60
60
|
@cli.command()
|
|
61
61
|
@click.argument("sources", nargs=-1)
|
|
62
|
+
@click.option(
|
|
63
|
+
"--auto-index", is_flag=True, help="Index all configured auto-index sources"
|
|
64
|
+
)
|
|
62
65
|
@with_app_context
|
|
63
66
|
@with_session
|
|
64
67
|
async def index(
|
|
65
68
|
session: AsyncSession,
|
|
66
69
|
app_context: AppContext,
|
|
67
70
|
sources: list[str],
|
|
71
|
+
*, # Force keyword-only arguments
|
|
72
|
+
auto_index: bool,
|
|
68
73
|
) -> None:
|
|
69
74
|
"""List indexes, or index data sources."""
|
|
70
75
|
log = structlog.get_logger(__name__)
|
|
@@ -78,6 +83,16 @@ async def index(
|
|
|
78
83
|
source_service=source_service,
|
|
79
84
|
)
|
|
80
85
|
|
|
86
|
+
if auto_index:
|
|
87
|
+
log.info("Auto-indexing configuration", config=app_context.auto_indexing)
|
|
88
|
+
auto_sources = app_context.auto_indexing.sources
|
|
89
|
+
if not auto_sources:
|
|
90
|
+
click.echo("No auto-index sources configured.")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
click.echo(f"Auto-indexing {len(auto_sources)} configured sources...")
|
|
94
|
+
sources = [source.uri for source in auto_sources]
|
|
95
|
+
|
|
81
96
|
if not sources:
|
|
82
97
|
log_event("kodit.cli.index.list")
|
|
83
98
|
# No source specified, list all indexes
|
kodit/config.py
CHANGED
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
|
-
from pydantic import BaseModel, Field
|
|
11
|
+
from pydantic import BaseModel, Field, field_validator
|
|
12
12
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
@@ -37,11 +37,45 @@ class Endpoint(BaseModel):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class Search(BaseModel):
|
|
40
|
-
"""Search
|
|
40
|
+
"""Search configuration."""
|
|
41
41
|
|
|
42
42
|
provider: Literal["sqlite", "vectorchord"] = Field(default="sqlite")
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
class AutoIndexingSource(BaseModel):
|
|
46
|
+
"""Configuration for a single auto-indexing source."""
|
|
47
|
+
|
|
48
|
+
uri: str = Field(description="URI of the source to index (git URL or local path)")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AutoIndexingConfig(BaseModel):
|
|
52
|
+
"""Configuration for auto-indexing."""
|
|
53
|
+
|
|
54
|
+
sources: list[AutoIndexingSource] = Field(
|
|
55
|
+
default_factory=list, description="List of sources to auto-index"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@field_validator("sources", mode="before")
|
|
59
|
+
@classmethod
|
|
60
|
+
def parse_sources(cls, v: Any) -> list[AutoIndexingSource]:
|
|
61
|
+
"""Parse sources from environment variables or other formats."""
|
|
62
|
+
if v is None:
|
|
63
|
+
return []
|
|
64
|
+
if isinstance(v, list):
|
|
65
|
+
return v
|
|
66
|
+
if isinstance(v, dict):
|
|
67
|
+
# Handle case where env vars are numbered keys like {'0': {'uri': '...'}}
|
|
68
|
+
sources = []
|
|
69
|
+
i = 0
|
|
70
|
+
while str(i) in v:
|
|
71
|
+
source_data = v[str(i)]
|
|
72
|
+
if isinstance(source_data, dict) and "uri" in source_data:
|
|
73
|
+
sources.append(AutoIndexingSource(uri=source_data["uri"]))
|
|
74
|
+
i += 1
|
|
75
|
+
return sources
|
|
76
|
+
return v
|
|
77
|
+
|
|
78
|
+
|
|
45
79
|
class AppContext(BaseSettings):
|
|
46
80
|
"""Global context for the kodit project. Provides a shared state for the app."""
|
|
47
81
|
|
|
@@ -50,7 +84,7 @@ class AppContext(BaseSettings):
|
|
|
50
84
|
env_file_encoding="utf-8",
|
|
51
85
|
env_nested_delimiter="_",
|
|
52
86
|
nested_model_default_partial_update=True,
|
|
53
|
-
|
|
87
|
+
extra="ignore",
|
|
54
88
|
)
|
|
55
89
|
|
|
56
90
|
data_dir: Path = Field(default=DEFAULT_BASE_DIR)
|
|
@@ -76,6 +110,9 @@ class AppContext(BaseSettings):
|
|
|
76
110
|
default_search: Search = Field(
|
|
77
111
|
default=Search(),
|
|
78
112
|
)
|
|
113
|
+
auto_indexing: AutoIndexingConfig | None = Field(
|
|
114
|
+
default=AutoIndexingConfig(), description="Auto-indexing configuration"
|
|
115
|
+
)
|
|
79
116
|
_db: Database | None = None
|
|
80
117
|
|
|
81
118
|
def model_post_init(self, _: Any) -> None:
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Service for automatically indexing configured sources."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from contextlib import suppress
|
|
6
|
+
|
|
7
|
+
import structlog
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
|
|
10
|
+
from kodit.application.factories.code_indexing_factory import (
|
|
11
|
+
create_code_indexing_application_service,
|
|
12
|
+
)
|
|
13
|
+
from kodit.config import AppContext
|
|
14
|
+
from kodit.domain.services.source_service import SourceService
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AutoIndexingService:
|
|
18
|
+
"""Service for automatically indexing configured sources."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
app_context: AppContext,
|
|
23
|
+
session_factory: Callable[[], AsyncSession],
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Initialize the auto-indexing service."""
|
|
26
|
+
self.app_context = app_context
|
|
27
|
+
self.session_factory = session_factory
|
|
28
|
+
self.log = structlog.get_logger(__name__)
|
|
29
|
+
self._indexing_task: asyncio.Task | None = None
|
|
30
|
+
|
|
31
|
+
async def start_background_indexing(self) -> None:
|
|
32
|
+
"""Start background indexing of configured sources."""
|
|
33
|
+
if (
|
|
34
|
+
not self.app_context.auto_indexing
|
|
35
|
+
or len(self.app_context.auto_indexing.sources) == 0
|
|
36
|
+
):
|
|
37
|
+
self.log.info("Auto-indexing is disabled (no sources configured)")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
auto_sources = [source.uri for source in self.app_context.auto_indexing.sources]
|
|
41
|
+
self.log.info("Starting background indexing", num_sources=len(auto_sources))
|
|
42
|
+
self._indexing_task = asyncio.create_task(self._index_sources(auto_sources))
|
|
43
|
+
|
|
44
|
+
async def _index_sources(self, sources: list[str]) -> None:
|
|
45
|
+
"""Index all configured sources in the background."""
|
|
46
|
+
async with self.session_factory() as session:
|
|
47
|
+
source_service = SourceService(
|
|
48
|
+
clone_dir=self.app_context.get_clone_dir(),
|
|
49
|
+
session_factory=lambda: session,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
service = create_code_indexing_application_service(
|
|
53
|
+
app_context=self.app_context,
|
|
54
|
+
session=session,
|
|
55
|
+
source_service=source_service,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
for source in sources:
|
|
59
|
+
try:
|
|
60
|
+
self.log.info("Auto-indexing source", source=source)
|
|
61
|
+
|
|
62
|
+
# Create source
|
|
63
|
+
s = await source_service.create(source)
|
|
64
|
+
|
|
65
|
+
# Create index
|
|
66
|
+
index = await service.create_index(s.id)
|
|
67
|
+
|
|
68
|
+
# Run indexing (without progress callback for background mode)
|
|
69
|
+
await service.run_index(index.id, progress_callback=None)
|
|
70
|
+
|
|
71
|
+
self.log.info("Successfully auto-indexed source", source=source)
|
|
72
|
+
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
self.log.exception(
|
|
75
|
+
"Failed to auto-index source", source=source, error=str(exc)
|
|
76
|
+
)
|
|
77
|
+
# Continue with other sources even if one fails
|
|
78
|
+
|
|
79
|
+
async def stop(self) -> None:
|
|
80
|
+
"""Stop background indexing."""
|
|
81
|
+
if self._indexing_task:
|
|
82
|
+
self._indexing_task.cancel()
|
|
83
|
+
with suppress(asyncio.CancelledError):
|
|
84
|
+
await self._indexing_task
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
kodit/.gitignore,sha256=ztkjgRwL9Uud1OEi36hGQeDGk3OLK1NfDEO8YqGYy8o,11
|
|
2
2
|
kodit/__init__.py,sha256=aEKHYninUq1yh6jaNfvJBYg-6fenpN132nJt1UU6Jxs,59
|
|
3
|
-
kodit/_version.py,sha256=
|
|
4
|
-
kodit/app.py,sha256=
|
|
5
|
-
kodit/cli.py,sha256=
|
|
6
|
-
kodit/config.py,sha256=
|
|
3
|
+
kodit/_version.py,sha256=AGmG_Lx0-9ztFw_7d9mYbaYuC-2abxE1oXOUNAY29YY,511
|
|
4
|
+
kodit/app.py,sha256=uv67TE83fZE7wrA7cz-sKosFrAXlKRr1B7fT-X_gMZQ,2103
|
|
5
|
+
kodit/cli.py,sha256=ihecBP7Og0F-wXYZ63TMeYzuqgFpfd7O-g4NRB3pufo,16864
|
|
6
|
+
kodit/config.py,sha256=myWtVujjd7xQh3tPK_zGC1L5D5PYoPknYvUBoMLJ1kM,5414
|
|
7
7
|
kodit/database.py,sha256=kI9yBm4uunsgV4-QeVoCBL0wLzU4kYmYv5qZilGnbPE,1740
|
|
8
8
|
kodit/log.py,sha256=sHPHYetlMcKTor2VaFLMyao1_fZ_xhuzqXCAt5F5UMU,8575
|
|
9
9
|
kodit/mcp.py,sha256=onmUbZ1-mnBkQOUF6KymNZMiTo19tt6pDOmyBQEz8Jg,6454
|
|
@@ -62,6 +62,7 @@ kodit/infrastructure/git/git_utils.py,sha256=2DH6cyTjDRwFfL5Bzt1y2w0DwHZNypbC6R0
|
|
|
62
62
|
kodit/infrastructure/ignore/__init__.py,sha256=VzFv8XOzHmsu0MEGnWVSF6KsgqLBmvHlRqAkT1Xb1MY,36
|
|
63
63
|
kodit/infrastructure/ignore/ignore_pattern_provider.py,sha256=9m2XCsgW87UBTfzHr6Z0Ns6WpzwkLir3zyBY3PwsgXk,2225
|
|
64
64
|
kodit/infrastructure/indexing/__init__.py,sha256=7UPRa2jwCAsa0Orsp6PqXSF8iIXJVzXHMFmrKkI9yH8,38
|
|
65
|
+
kodit/infrastructure/indexing/auto_indexing_service.py,sha256=uXggladN3PTU5Jzhz0Kq-0aObvq3Dq9YbjYKCSkaQA8,3131
|
|
65
66
|
kodit/infrastructure/indexing/fusion_service.py,sha256=mXUUcx3-8e75mWkxXMfl30HIoFXrTNHzB1w90MmEbak,1806
|
|
66
67
|
kodit/infrastructure/indexing/index_repository.py,sha256=4aSCBE_Gn9ihOx_kXOpUTTIv6_Q71-VRFHEBgpWaAEw,8906
|
|
67
68
|
kodit/infrastructure/indexing/indexing_factory.py,sha256=LPjPCps_wJ9M_fZGRP02bfc2pvYc50ZSTYI99XwRRPg,918
|
|
@@ -94,8 +95,8 @@ kodit/migrations/versions/85155663351e_initial.py,sha256=Cg7zlF871o9ShV5rQMQ1v7h
|
|
|
94
95
|
kodit/migrations/versions/9e53ea8bb3b0_add_authors.py,sha256=a32Zm8KUQyiiLkjKNPYdaJDgjW6VsV-GhaLnPnK_fpI,3884
|
|
95
96
|
kodit/migrations/versions/__init__.py,sha256=9-lHzptItTzq_fomdIRBegQNm4Znx6pVjwD4MiqRIdo,36
|
|
96
97
|
kodit/migrations/versions/c3f5137d30f5_index_all_the_things.py,sha256=rI8LmjF-I2OMxZ2nOIF_NRmqOLXe45hL_iz_nx97DTQ,1680
|
|
97
|
-
kodit-0.
|
|
98
|
-
kodit-0.
|
|
99
|
-
kodit-0.
|
|
100
|
-
kodit-0.
|
|
101
|
-
kodit-0.
|
|
98
|
+
kodit-0.3.0.dist-info/METADATA,sha256=7VZ989c731WalePCDWB1KSIUKJ9vS5hcgYVuI2Souiw,5867
|
|
99
|
+
kodit-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
100
|
+
kodit-0.3.0.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
|
|
101
|
+
kodit-0.3.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
102
|
+
kodit-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|