kodit 0.1.7__py3-none-any.whl → 0.1.9__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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.7'
21
- __version_tuple__ = version_tuple = (0, 1, 7)
20
+ __version__ = version = '0.1.9'
21
+ __version_tuple__ = version_tuple = (0, 1, 9)
kodit/cli.py CHANGED
@@ -123,12 +123,6 @@ async def index(
123
123
  return
124
124
  # Handle source indexing
125
125
  for source in sources:
126
- if source.startswith("https://"):
127
- msg = "Web or git indexing is not implemented yet"
128
- raise click.UsageError(msg)
129
- if source.startswith("git"):
130
- msg = "Git indexing is not implemented yet"
131
- raise click.UsageError(msg)
132
126
  if Path(source).is_file():
133
127
  msg = "File indexing is not implemented yet"
134
128
  raise click.UsageError(msg)
@@ -8,7 +8,7 @@ and retrieving index information with their associated metadata.
8
8
  from datetime import UTC, datetime
9
9
  from typing import TypeVar
10
10
 
11
- from sqlalchemy import func, select
11
+ from sqlalchemy import delete, func, select
12
12
  from sqlalchemy.ext.asyncio import AsyncSession
13
13
 
14
14
  from kodit.indexing.models import Index, Snippet
@@ -63,6 +63,17 @@ class IndexRepository:
63
63
  result = await self.session.execute(query)
64
64
  return result.scalar_one_or_none()
65
65
 
66
+ async def get_by_source_id(self, source_id: int) -> Index | None:
67
+ """Get an index by its source ID.
68
+
69
+ Args:
70
+ source_id: The ID of the source to retrieve an index for.
71
+
72
+ """
73
+ query = select(Index).where(Index.source_id == source_id)
74
+ result = await self.session.execute(query)
75
+ return result.scalar_one_or_none()
76
+
66
77
  async def files_for_index(self, index_id: int) -> list[File]:
67
78
  """Get all files for an index.
68
79
 
@@ -122,6 +133,17 @@ class IndexRepository:
122
133
  self.session.add(snippet)
123
134
  await self.session.commit()
124
135
 
136
+ async def delete_all_snippets(self, index_id: int) -> None:
137
+ """Delete all snippets for an index.
138
+
139
+ Args:
140
+ index_id: The ID of the index to delete snippets for.
141
+
142
+ """
143
+ query = delete(Snippet).where(Snippet.index_id == index_id)
144
+ await self.session.execute(query)
145
+ await self.session.commit()
146
+
125
147
  async def get_snippets_for_index(self, index_id: int) -> list[Snippet]:
126
148
  """Get all snippets for an index.
127
149
 
kodit/indexing/service.py CHANGED
@@ -83,7 +83,10 @@ class IndexService:
83
83
  # Check if the source exists
84
84
  source = await self.source_service.get(source_id)
85
85
 
86
- index = await self.repository.create(source.id)
86
+ # Check if the index already exists
87
+ index = await self.repository.get_by_source_id(source.id)
88
+ if not index:
89
+ index = await self.repository.create(source.id)
87
90
  return IndexView(
88
91
  id=index.id,
89
92
  created_at=index.created_at,
@@ -119,6 +122,9 @@ class IndexService:
119
122
  msg = f"Index not found: {index_id}"
120
123
  raise ValueError(msg)
121
124
 
125
+ # First delete all old snippets, if they exist
126
+ await self.repository.delete_all_snippets(index_id)
127
+
122
128
  # Create snippets for supported file types
123
129
  await self._create_snippets(index_id)
124
130
 
kodit/sources/service.py CHANGED
@@ -13,6 +13,7 @@ from hashlib import sha256
13
13
  from pathlib import Path
14
14
 
15
15
  import aiofiles
16
+ import git
16
17
  import pydantic
17
18
  import structlog
18
19
  from tqdm import tqdm
@@ -98,8 +99,19 @@ class SourceService:
98
99
  parsed = urisplit(uri_or_path_like)
99
100
  if parsed.scheme == "file":
100
101
  return await self._create_folder_source(Path(parsed.path))
101
- msg = f"Unsupported source type: {uri_or_path_like}"
102
- raise ValueError(msg)
102
+ if parsed.scheme in ("git", "http", "https") and parsed.path.endswith(
103
+ ".git"
104
+ ):
105
+ return await self._create_git_source(uri_or_path_like)
106
+
107
+ # Try adding a .git suffix, sometimes people just pass the url
108
+ if not uri_or_path_like.endswith(".git"):
109
+ uri_or_path_like = uri_or_path_like + ".git"
110
+ try:
111
+ return await self._create_git_source(uri_or_path_like)
112
+ except ValueError:
113
+ pass
114
+
103
115
  msg = f"Unsupported source type: {uri_or_path_like}"
104
116
  raise ValueError(msg)
105
117
 
@@ -110,46 +122,98 @@ class SourceService:
110
122
  directory: The path to the local directory.
111
123
 
112
124
  Raises:
113
- ValueError: If the folder doesn't exist or is already added.
125
+ ValueError: If the folder doesn't exist.
126
+ SourceAlreadyExistsError: If the folder is already added.
114
127
 
115
128
  """
116
129
  # Resolve the directory to an absolute path
117
130
  directory = directory.expanduser().resolve()
118
131
 
119
- # Check if the folder exists
120
- if not directory.exists():
121
- msg = f"Folder does not exist: {directory}"
122
- raise ValueError(msg)
132
+ source = await self.repository.get_source_by_uri(directory.as_uri())
133
+ if source:
134
+ self.log.info("Source already exists, reusing...", source_id=source.id)
135
+ else:
136
+ # Check if the folder exists
137
+ if not directory.exists():
138
+ msg = f"Folder does not exist: {directory}"
139
+ raise ValueError(msg)
140
+
141
+ # Check if the folder is already added
142
+ if await self.repository.get_source_by_uri(directory.as_uri()):
143
+ msg = f"Directory already added: {directory}"
144
+ raise ValueError(msg)
145
+
146
+ # Clone into a local directory
147
+ clone_path = self.clone_dir / directory.as_posix().replace("/", "_")
148
+ clone_path.mkdir(parents=True, exist_ok=True)
149
+
150
+ # Copy all files recursively, preserving directory structure, ignoring
151
+ # hidden files
152
+ shutil.copytree(
153
+ directory,
154
+ clone_path,
155
+ ignore=shutil.ignore_patterns(".*"),
156
+ dirs_exist_ok=True,
157
+ )
123
158
 
124
- # Check if the folder is already added
125
- if await self.repository.get_source_by_uri(directory.as_uri()):
126
- msg = f"Directory already added: {directory}"
127
- raise ValueError(msg)
159
+ source = await self.repository.create_source(
160
+ Source(uri=directory.as_uri(), cloned_path=str(clone_path)),
161
+ )
128
162
 
129
- # Clone into a local directory
130
- clone_path = self.clone_dir / directory.as_posix().replace("/", "_")
131
- clone_path.mkdir(parents=True, exist_ok=True)
132
-
133
- # Copy all files recursively, preserving directory structure, ignoring hidden
134
- # files
135
- shutil.copytree(
136
- directory,
137
- clone_path,
138
- ignore=shutil.ignore_patterns(".*"),
139
- dirs_exist_ok=True,
140
- )
163
+ # Add all files to the source
164
+ # Count total files for progress bar
165
+ file_count = sum(1 for _ in clone_path.rglob("*") if _.is_file())
166
+
167
+ # Process each file in the source directory
168
+ for path in tqdm(clone_path.rglob("*"), total=file_count):
169
+ await self._process_file(source.id, path.absolute())
141
170
 
142
- source = await self.repository.create_source(
143
- Source(uri=directory.as_uri(), cloned_path=str(clone_path)),
171
+ return SourceView(
172
+ id=source.id,
173
+ uri=source.uri,
174
+ cloned_path=Path(source.cloned_path),
175
+ created_at=source.created_at,
176
+ num_files=await self.repository.num_files_for_source(source.id),
144
177
  )
145
178
 
146
- # Add all files to the source
147
- # Count total files for progress bar
148
- file_count = sum(1 for _ in clone_path.rglob("*") if _.is_file())
179
+ async def _create_git_source(self, uri: str) -> SourceView:
180
+ """Create a git source.
181
+
182
+ Args:
183
+ uri: The URI of the git repository.
184
+
185
+ Raises:
186
+ ValueError: If the repository cloning fails.
187
+
188
+ """
189
+ # Check if the repository is already added
190
+ source = await self.repository.get_source_by_uri(uri)
191
+
192
+ if source:
193
+ self.log.info("Source already exists, reusing...", source_id=source.id)
194
+ else:
195
+ # Create a unique directory name for the clone
196
+ clone_path = self.clone_dir / uri.replace("/", "_").replace(":", "_")
197
+ clone_path.mkdir(parents=True, exist_ok=True)
198
+
199
+ try:
200
+ # Clone the repository
201
+ git.Repo.clone_from(uri, clone_path)
202
+ except git.GitCommandError as e:
203
+ msg = f"Failed to clone repository: {e}"
204
+ raise ValueError(msg) from e
205
+
206
+ source = await self.repository.create_source(
207
+ Source(uri=uri, cloned_path=str(clone_path)),
208
+ )
209
+
210
+ # Add all files to the source
211
+ # Count total files for progress bar
212
+ file_count = sum(1 for _ in clone_path.rglob("*") if _.is_file())
149
213
 
150
- # Process each file in the source directory
151
- for path in tqdm(clone_path.rglob("*"), total=file_count):
152
- await self._process_file(source.id, path.absolute())
214
+ # Process each file in the source directory
215
+ for path in tqdm(clone_path.rglob("*"), total=file_count):
216
+ await self._process_file(source.id, path.absolute())
153
217
 
154
218
  return SourceView(
155
219
  id=source.id,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kodit
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Code indexing for better AI code generation
5
5
  Project-URL: Homepage, https://docs.helixml.tech/kodit/
6
6
  Project-URL: Documentation, https://docs.helixml.tech/kodit/
@@ -28,6 +28,7 @@ Requires-Dist: colorama>=0.4.6
28
28
  Requires-Dist: dotenv>=0.9.9
29
29
  Requires-Dist: fastapi[standard]>=0.115.12
30
30
  Requires-Dist: fastmcp>=2.3.3
31
+ Requires-Dist: gitpython>=3.1.44
31
32
  Requires-Dist: httpx-retries>=0.3.2
32
33
  Requires-Dist: httpx>=0.28.1
33
34
  Requires-Dist: posthog>=4.0.1
@@ -1,8 +1,8 @@
1
1
  kodit/.gitignore,sha256=ztkjgRwL9Uud1OEi36hGQeDGk3OLK1NfDEO8YqGYy8o,11
2
2
  kodit/__init__.py,sha256=aEKHYninUq1yh6jaNfvJBYg-6fenpN132nJt1UU6Jxs,59
3
- kodit/_version.py,sha256=W_EoL8cAL4KhujvbYWEpb9NqRLbbrH0T024lJvRRWHI,511
3
+ kodit/_version.py,sha256=bhntibG3PKk5Ai3XlSNEV8gj-ffItuKloY6vzWn6swo,511
4
4
  kodit/app.py,sha256=Mr5BFHOHx5zppwjC4XPWVvHjwgl1yrKbUjTWXKubJQM,891
5
- kodit/cli.py,sha256=x1zw2zOlGhhU6D3E-GU3cMw3l9CqKC76geQREAQKweY,6915
5
+ kodit/cli.py,sha256=bsfURvGKZzpHkChnTlatI0nXHV3KV_6vJnUJ2fQEAfM,6637
6
6
  kodit/config.py,sha256=nlm9U-nVx5riH2SrU1XY4XcCMhQK4DrwO_1H8bPOBjA,2927
7
7
  kodit/database.py,sha256=vtTlmrXHyHJH3Ek-twZTCqEjB0jun-NncALFze2fqhA,2350
8
8
  kodit/logging.py,sha256=cFEQXWI27LzWScSxly9ApwkbBDamUG17pA-jEfVakXQ,5316
@@ -18,8 +18,8 @@ kodit/bm25/__init__.py,sha256=j8zyriNWhbwE5Lbybzg1hQAhANlU9mKHWw4beeUR6og,19
18
18
  kodit/bm25/bm25.py,sha256=3wyNRSrTaYqV7s4R1D6X0NpCf22PuFK2_uc8YapzYLE,2263
19
19
  kodit/indexing/__init__.py,sha256=cPyi2Iej3G1JFWlWr7X80_UrsMaTu5W5rBwgif1B3xo,75
20
20
  kodit/indexing/models.py,sha256=sZIhGwvL4Dw0QTWFxrjfWctSLkAoDT6fv5DlGz8-Fr8,1258
21
- kodit/indexing/repository.py,sha256=C020FGpIfTZmVZg7NH04kVuffWv7r7m-82Pdex8CItg,4388
22
- kodit/indexing/service.py,sha256=7vHqevve-PnKHP2pDfyrW5n3AXVOghABWNsNTw588KY,5499
21
+ kodit/indexing/repository.py,sha256=ZicLPXPKQxW6NnY_anmZ4nI1-FGkrJsqjg0NK-vvnTY,5117
22
+ kodit/indexing/service.py,sha256=rLWYI70VytlJAyZtQC5Xpqtj9f3EzbivzgeM_1L9BUU,5751
23
23
  kodit/retreival/__init__.py,sha256=33PhJU-3gtsqYq6A1UkaLNKbev_Zee9Lq6dYC59-CsA,69
24
24
  kodit/retreival/repository.py,sha256=1lqGgJHsBmvMGMzEYa-hrdXg2q7rqtYPl1cvBb7jMRE,3119
25
25
  kodit/retreival/service.py,sha256=9wvURtPPJVvPUWNIC2waIrJMxcm1Ka1J_xDEOEedAFU,2007
@@ -32,9 +32,9 @@ kodit/snippets/languages/python.scm,sha256=ee85R9PBzwye3IMTE7-iVoKWd_ViU3EJISTyr
32
32
  kodit/sources/__init__.py,sha256=1NTZyPdjThVQpZO1Mp1ColVsS7sqYanOVLqnoqV9Ipo,83
33
33
  kodit/sources/models.py,sha256=xb42CaNDO1CUB8SIW-xXMrB6Ji8cFw-yeJ550xBEg9Q,2398
34
34
  kodit/sources/repository.py,sha256=mGJrHWH6Uo8YABdoojHFbzaf_jW-2ywJpAHIa1gnc3U,3401
35
- kodit/sources/service.py,sha256=cBCxnOQKwGNi2e13_3Vue8MylAaUxb9XG4IgM636la0,6712
36
- kodit-0.1.7.dist-info/METADATA,sha256=8doJ6TfmVkn-OTLSbgRlDut6YxFbmBKs1L0rgZUlxUQ,2181
37
- kodit-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
- kodit-0.1.7.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
39
- kodit-0.1.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
40
- kodit-0.1.7.dist-info/RECORD,,
35
+ kodit/sources/service.py,sha256=hqAjGFVhvtePhMrK1Aprj__Mq2PLjVq8CsWMBoA3_Qw,9217
36
+ kodit-0.1.9.dist-info/METADATA,sha256=MAqVxrLPrTV3Ihcix_3YHQNq9qyuD1OEavYHV76qli8,2214
37
+ kodit-0.1.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
+ kodit-0.1.9.dist-info/entry_points.txt,sha256=hoTn-1aKyTItjnY91fnO-rV5uaWQLQ-Vi7V5et2IbHY,40
39
+ kodit-0.1.9.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
40
+ kodit-0.1.9.dist-info/RECORD,,
File without changes