kodit 0.4.1__py3-none-any.whl → 0.4.3__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 +9 -2
- kodit/application/factories/code_indexing_factory.py +62 -13
- kodit/application/factories/reporting_factory.py +32 -0
- kodit/application/services/auto_indexing_service.py +41 -33
- kodit/application/services/code_indexing_application_service.py +137 -138
- kodit/application/services/indexing_worker_service.py +26 -30
- kodit/application/services/queue_service.py +12 -14
- kodit/application/services/reporting.py +104 -0
- kodit/application/services/sync_scheduler.py +21 -20
- kodit/cli.py +71 -85
- kodit/config.py +26 -3
- kodit/database.py +2 -1
- kodit/domain/entities.py +99 -1
- kodit/domain/protocols.py +34 -1
- kodit/domain/services/bm25_service.py +1 -6
- kodit/domain/services/index_service.py +23 -57
- kodit/domain/services/task_status_query_service.py +19 -0
- kodit/domain/value_objects.py +53 -8
- kodit/infrastructure/api/v1/dependencies.py +40 -12
- kodit/infrastructure/api/v1/routers/indexes.py +45 -0
- kodit/infrastructure/api/v1/schemas/task_status.py +39 -0
- kodit/infrastructure/cloning/git/working_copy.py +43 -7
- kodit/infrastructure/embedding/embedding_factory.py +8 -3
- kodit/infrastructure/embedding/embedding_providers/litellm_embedding_provider.py +48 -55
- kodit/infrastructure/enrichment/local_enrichment_provider.py +41 -30
- kodit/infrastructure/git/git_utils.py +3 -2
- kodit/infrastructure/mappers/index_mapper.py +1 -0
- kodit/infrastructure/mappers/task_status_mapper.py +85 -0
- kodit/infrastructure/reporting/__init__.py +1 -0
- kodit/infrastructure/reporting/db_progress.py +23 -0
- kodit/infrastructure/reporting/log_progress.py +37 -0
- kodit/infrastructure/reporting/tdqm_progress.py +38 -0
- kodit/infrastructure/sqlalchemy/embedding_repository.py +47 -68
- kodit/infrastructure/sqlalchemy/entities.py +89 -2
- kodit/infrastructure/sqlalchemy/index_repository.py +274 -236
- kodit/infrastructure/sqlalchemy/task_repository.py +55 -39
- kodit/infrastructure/sqlalchemy/task_status_repository.py +79 -0
- kodit/infrastructure/sqlalchemy/unit_of_work.py +59 -0
- kodit/mcp.py +15 -3
- kodit/migrations/env.py +0 -1
- kodit/migrations/versions/b9cd1c3fd762_add_task_status.py +77 -0
- {kodit-0.4.1.dist-info → kodit-0.4.3.dist-info}/METADATA +1 -1
- {kodit-0.4.1.dist-info → kodit-0.4.3.dist-info}/RECORD +47 -40
- kodit/domain/interfaces.py +0 -27
- kodit/infrastructure/ui/__init__.py +0 -1
- kodit/infrastructure/ui/progress.py +0 -170
- kodit/infrastructure/ui/spinner.py +0 -74
- kodit/reporting.py +0 -78
- {kodit-0.4.1.dist-info → kodit-0.4.3.dist-info}/WHEEL +0 -0
- {kodit-0.4.1.dist-info → kodit-0.4.3.dist-info}/entry_points.txt +0 -0
- {kodit-0.4.1.dist-info → kodit-0.4.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
"""Progress UI implementations."""
|
|
2
|
-
|
|
3
|
-
from collections.abc import Callable
|
|
4
|
-
|
|
5
|
-
import structlog
|
|
6
|
-
from tqdm import tqdm # type: ignore[import-untyped]
|
|
7
|
-
|
|
8
|
-
from kodit.domain.interfaces import ProgressCallback
|
|
9
|
-
from kodit.domain.value_objects import ProgressEvent
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TQDMProgressCallback(ProgressCallback):
|
|
13
|
-
"""TQDM-based progress callback implementation."""
|
|
14
|
-
|
|
15
|
-
def __init__(self, pbar: "tqdm") -> None:
|
|
16
|
-
"""Initialize with a TQDM progress bar."""
|
|
17
|
-
self.pbar = pbar
|
|
18
|
-
|
|
19
|
-
async def on_progress(self, event: ProgressEvent) -> None:
|
|
20
|
-
"""Update the TQDM progress bar."""
|
|
21
|
-
# Update total if it changes
|
|
22
|
-
if event.total != self.pbar.total:
|
|
23
|
-
self.pbar.total = event.total
|
|
24
|
-
|
|
25
|
-
# Update the progress bar
|
|
26
|
-
self.pbar.n = event.current
|
|
27
|
-
self.pbar.refresh()
|
|
28
|
-
|
|
29
|
-
# Update description if message is provided
|
|
30
|
-
if event.message:
|
|
31
|
-
# Fix the event message to a specific size so it's not jumping around
|
|
32
|
-
# If it's too small, add spaces
|
|
33
|
-
# If it's too large, truncate
|
|
34
|
-
if len(event.message) < 30:
|
|
35
|
-
self.pbar.set_description(
|
|
36
|
-
event.message + " " * (30 - len(event.message))
|
|
37
|
-
)
|
|
38
|
-
else:
|
|
39
|
-
self.pbar.set_description(event.message[-30:])
|
|
40
|
-
|
|
41
|
-
async def on_complete(self, operation: str) -> None:
|
|
42
|
-
"""Complete the progress bar."""
|
|
43
|
-
# TQDM will handle cleanup with leave=False
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class LogProgressCallback(ProgressCallback):
|
|
47
|
-
"""Log-based progress callback for server environments."""
|
|
48
|
-
|
|
49
|
-
def __init__(self, milestone_interval: int = 10) -> None:
|
|
50
|
-
"""Initialize with milestone logging interval.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
milestone_interval: Percentage interval for logging (default: 10%)
|
|
54
|
-
|
|
55
|
-
"""
|
|
56
|
-
self.milestone_interval = milestone_interval
|
|
57
|
-
self._last_logged_percentage = -1
|
|
58
|
-
self.log = structlog.get_logger()
|
|
59
|
-
|
|
60
|
-
async def on_progress(self, event: ProgressEvent) -> None:
|
|
61
|
-
"""Log progress at milestone intervals."""
|
|
62
|
-
percentage = int(event.percentage)
|
|
63
|
-
|
|
64
|
-
# Log at milestone intervals (0%, 10%, 20%, etc.)
|
|
65
|
-
milestone = (percentage // self.milestone_interval) * self.milestone_interval
|
|
66
|
-
|
|
67
|
-
if milestone > self._last_logged_percentage and milestone <= percentage:
|
|
68
|
-
self.log.info(
|
|
69
|
-
"Progress milestone reached",
|
|
70
|
-
operation=event.operation,
|
|
71
|
-
percentage=milestone,
|
|
72
|
-
current=event.current,
|
|
73
|
-
total=event.total,
|
|
74
|
-
message=event.message,
|
|
75
|
-
)
|
|
76
|
-
self._last_logged_percentage = milestone
|
|
77
|
-
|
|
78
|
-
async def on_complete(self, operation: str) -> None:
|
|
79
|
-
"""Log completion of the operation."""
|
|
80
|
-
self.log.info("Operation completed", operation=operation)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class LazyProgressCallback(ProgressCallback):
|
|
84
|
-
"""Progress callback that only shows progress when there's actual work to do."""
|
|
85
|
-
|
|
86
|
-
def __init__(self, create_pbar_func: Callable[[], tqdm]) -> None:
|
|
87
|
-
"""Initialize with a function that creates a progress bar."""
|
|
88
|
-
self.create_pbar_func = create_pbar_func
|
|
89
|
-
self._callback: ProgressCallback | None = None
|
|
90
|
-
self._has_work = False
|
|
91
|
-
|
|
92
|
-
async def on_progress(self, event: ProgressEvent) -> None:
|
|
93
|
-
"""Update progress, creating the actual callback if needed."""
|
|
94
|
-
if not self._has_work:
|
|
95
|
-
self._has_work = True
|
|
96
|
-
# Only create the progress bar when we actually have work to do
|
|
97
|
-
pbar = self.create_pbar_func()
|
|
98
|
-
self._callback = TQDMProgressCallback(pbar)
|
|
99
|
-
|
|
100
|
-
if self._callback:
|
|
101
|
-
await self._callback.on_progress(event)
|
|
102
|
-
|
|
103
|
-
async def on_complete(self, operation: str) -> None:
|
|
104
|
-
"""Complete the progress operation."""
|
|
105
|
-
if self._callback:
|
|
106
|
-
await self._callback.on_complete(operation)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class MultiStageProgressCallback(ProgressCallback):
|
|
110
|
-
"""Progress callback that handles multiple stages with separate progress bars."""
|
|
111
|
-
|
|
112
|
-
def __init__(self, create_pbar_func: Callable[[str], tqdm]) -> None:
|
|
113
|
-
"""Initialize with a function that creates progress bars."""
|
|
114
|
-
self.create_pbar_func = create_pbar_func
|
|
115
|
-
self._current_callback: ProgressCallback | None = None
|
|
116
|
-
self._current_operation: str | None = None
|
|
117
|
-
|
|
118
|
-
async def on_progress(self, event: ProgressEvent) -> None:
|
|
119
|
-
"""Update progress for the current operation."""
|
|
120
|
-
# If this is a new operation, create a new progress bar
|
|
121
|
-
if self._current_operation != event.operation:
|
|
122
|
-
# Create a new progress bar for this operation
|
|
123
|
-
pbar = self.create_pbar_func(event.operation)
|
|
124
|
-
self._current_callback = TQDMProgressCallback(pbar)
|
|
125
|
-
self._current_operation = event.operation
|
|
126
|
-
|
|
127
|
-
# Update the current progress bar
|
|
128
|
-
if self._current_callback:
|
|
129
|
-
await self._current_callback.on_progress(event)
|
|
130
|
-
|
|
131
|
-
async def on_complete(self, operation: str) -> None:
|
|
132
|
-
"""Complete the current operation."""
|
|
133
|
-
if self._current_callback and self._current_operation == operation:
|
|
134
|
-
await self._current_callback.on_complete(operation)
|
|
135
|
-
self._current_callback = None
|
|
136
|
-
self._current_operation = None
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def create_progress_bar(desc: str = "Processing", unit: str = "items") -> "tqdm":
|
|
140
|
-
"""Create a progress bar with the given description and unit."""
|
|
141
|
-
from tqdm import tqdm
|
|
142
|
-
|
|
143
|
-
return tqdm(
|
|
144
|
-
desc=desc,
|
|
145
|
-
unit=unit,
|
|
146
|
-
leave=False,
|
|
147
|
-
dynamic_ncols=True,
|
|
148
|
-
total=None, # Will be set dynamically
|
|
149
|
-
position=0, # Position at top
|
|
150
|
-
mininterval=0.1, # Update at most every 0.1 seconds
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def create_lazy_progress_callback() -> LazyProgressCallback:
|
|
155
|
-
"""Create a lazy progress callback that only shows progress when needed."""
|
|
156
|
-
return LazyProgressCallback(
|
|
157
|
-
lambda: create_progress_bar("Processing files", "files")
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def create_multi_stage_progress_callback() -> MultiStageProgressCallback:
|
|
162
|
-
"""Create a multi-stage progress callback for indexing operations."""
|
|
163
|
-
return MultiStageProgressCallback(
|
|
164
|
-
lambda operation: create_progress_bar(operation, "items")
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def create_log_progress_callback(milestone_interval: int = 10) -> LogProgressCallback:
|
|
169
|
-
"""Create a log-based progress callback for server environments."""
|
|
170
|
-
return LogProgressCallback(milestone_interval=milestone_interval)
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"""Spinner for long-running tasks in the UI layer."""
|
|
2
|
-
|
|
3
|
-
import itertools
|
|
4
|
-
import sys
|
|
5
|
-
import threading
|
|
6
|
-
import time
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Spinner:
|
|
10
|
-
"""Spinner for long-running tasks.
|
|
11
|
-
|
|
12
|
-
This class provides visual feedback for long-running operations by displaying
|
|
13
|
-
a spinning animation in the terminal. It's designed to be used as a context
|
|
14
|
-
manager for operations that may take some time to complete.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def __init__(self, delay: float = 0.1) -> None:
|
|
18
|
-
"""Initialize the spinner.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
delay: The delay between spinner updates in seconds.
|
|
22
|
-
|
|
23
|
-
"""
|
|
24
|
-
self.spinner = itertools.cycle(["-", "/", "|", "\\"])
|
|
25
|
-
self.delay = delay
|
|
26
|
-
self.busy = False
|
|
27
|
-
self.spinner_visible = False
|
|
28
|
-
|
|
29
|
-
def write_next(self) -> None:
|
|
30
|
-
"""Write the next character of the spinner."""
|
|
31
|
-
with self._screen_lock:
|
|
32
|
-
if not self.spinner_visible:
|
|
33
|
-
sys.stdout.write(next(self.spinner))
|
|
34
|
-
self.spinner_visible = True
|
|
35
|
-
sys.stdout.flush()
|
|
36
|
-
|
|
37
|
-
def remove_spinner(self, cleanup: bool = False) -> None: # noqa: FBT001, FBT002
|
|
38
|
-
"""Remove the spinner.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
cleanup: Whether to clean up the spinner display.
|
|
42
|
-
|
|
43
|
-
"""
|
|
44
|
-
with self._screen_lock:
|
|
45
|
-
if self.spinner_visible:
|
|
46
|
-
sys.stdout.write("\b")
|
|
47
|
-
self.spinner_visible = False
|
|
48
|
-
if cleanup:
|
|
49
|
-
sys.stdout.write(" ") # overwrite spinner with blank
|
|
50
|
-
sys.stdout.write("\r") # move to next line
|
|
51
|
-
sys.stdout.flush()
|
|
52
|
-
|
|
53
|
-
def spinner_task(self) -> None:
|
|
54
|
-
"""Task that runs the spinner."""
|
|
55
|
-
while self.busy:
|
|
56
|
-
self.write_next()
|
|
57
|
-
time.sleep(self.delay)
|
|
58
|
-
self.remove_spinner()
|
|
59
|
-
|
|
60
|
-
def __enter__(self) -> None:
|
|
61
|
-
"""Enter the context manager."""
|
|
62
|
-
if sys.stdout.isatty():
|
|
63
|
-
self._screen_lock = threading.Lock()
|
|
64
|
-
self.busy = True
|
|
65
|
-
self.thread = threading.Thread(target=self.spinner_task)
|
|
66
|
-
self.thread.start()
|
|
67
|
-
|
|
68
|
-
def __exit__(self, exception: object, value: object, tb: object) -> None:
|
|
69
|
-
"""Exit the context manager."""
|
|
70
|
-
if sys.stdout.isatty():
|
|
71
|
-
self.busy = False
|
|
72
|
-
self.remove_spinner(cleanup=True)
|
|
73
|
-
else:
|
|
74
|
-
sys.stdout.write("\r")
|
kodit/reporting.py
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"""Unified logging and progress-reporting helper.
|
|
2
|
-
|
|
3
|
-
This utility consolidates the repeated pattern where services:
|
|
4
|
-
1. Log a message (usually via structlog) and
|
|
5
|
-
2. Emit a ProgressEvent via a ProgressCallback.
|
|
6
|
-
|
|
7
|
-
Using Reporter removes boiler-plate and guarantees consistent telemetry.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import structlog
|
|
11
|
-
|
|
12
|
-
from kodit.domain.interfaces import NullProgressCallback, ProgressCallback
|
|
13
|
-
from kodit.domain.value_objects import ProgressEvent
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Reporter:
|
|
17
|
-
"""Emit log and progress updates with a single call."""
|
|
18
|
-
|
|
19
|
-
def __init__(
|
|
20
|
-
self,
|
|
21
|
-
logger: structlog.BoundLogger | None = None,
|
|
22
|
-
progress: ProgressCallback | None = None,
|
|
23
|
-
) -> None:
|
|
24
|
-
"""Initialize the reporter."""
|
|
25
|
-
self.log: structlog.BoundLogger = logger or structlog.get_logger(__name__)
|
|
26
|
-
self.progress: ProgressCallback = progress or NullProgressCallback()
|
|
27
|
-
|
|
28
|
-
# ---------------------------------------------------------------------
|
|
29
|
-
# Life-cycle helpers
|
|
30
|
-
# ---------------------------------------------------------------------
|
|
31
|
-
async def start(
|
|
32
|
-
self, operation: str, total: int, message: str | None = None
|
|
33
|
-
) -> None:
|
|
34
|
-
"""Log *operation.start* and emit initial ProgressEvent."""
|
|
35
|
-
self.log.debug(
|
|
36
|
-
"operation.start", operation=operation, total=total, message=message
|
|
37
|
-
)
|
|
38
|
-
await self.progress.on_progress(
|
|
39
|
-
ProgressEvent(operation=operation, current=0, total=total, message=message)
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
async def step(
|
|
43
|
-
self,
|
|
44
|
-
operation: str,
|
|
45
|
-
current: int,
|
|
46
|
-
total: int,
|
|
47
|
-
message: str | None = None,
|
|
48
|
-
) -> None:
|
|
49
|
-
"""Emit an intermediate progress step (no log by default)."""
|
|
50
|
-
await self.progress.on_progress(
|
|
51
|
-
ProgressEvent(
|
|
52
|
-
operation=operation, current=current, total=total, message=message
|
|
53
|
-
)
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
async def done(self, operation: str, message: str | None = None) -> None:
|
|
57
|
-
"""Log *operation.done* and emit completion event."""
|
|
58
|
-
self.log.debug("operation.done", operation=operation, message=message)
|
|
59
|
-
await self.progress.on_complete(operation)
|
|
60
|
-
|
|
61
|
-
async def advance(
|
|
62
|
-
self,
|
|
63
|
-
operation: str,
|
|
64
|
-
current: int,
|
|
65
|
-
total: int,
|
|
66
|
-
message: str | None = None,
|
|
67
|
-
log_every: int | None = None,
|
|
68
|
-
) -> None:
|
|
69
|
-
"""Emit step; optionally log when *current % log_every == 0*."""
|
|
70
|
-
if log_every and current % log_every == 0:
|
|
71
|
-
self.log.debug(
|
|
72
|
-
"operation.progress",
|
|
73
|
-
operation=operation,
|
|
74
|
-
current=current,
|
|
75
|
-
total=total,
|
|
76
|
-
message=message,
|
|
77
|
-
)
|
|
78
|
-
await self.step(operation, current, total, message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|