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.

Files changed (135) hide show
  1. kodit/_version.py +2 -2
  2. kodit/app.py +51 -23
  3. kodit/application/factories/reporting_factory.py +6 -2
  4. kodit/application/factories/server_factory.py +353 -0
  5. kodit/application/services/code_search_application_service.py +144 -0
  6. kodit/application/services/commit_indexing_application_service.py +700 -0
  7. kodit/application/services/indexing_worker_service.py +13 -44
  8. kodit/application/services/queue_service.py +24 -3
  9. kodit/application/services/reporting.py +0 -2
  10. kodit/application/services/sync_scheduler.py +15 -31
  11. kodit/cli.py +2 -753
  12. kodit/cli_utils.py +2 -9
  13. kodit/config.py +4 -97
  14. kodit/database.py +38 -1
  15. kodit/domain/enrichments/__init__.py +1 -0
  16. kodit/domain/enrichments/architecture/__init__.py +1 -0
  17. kodit/domain/enrichments/architecture/architecture.py +20 -0
  18. kodit/domain/enrichments/architecture/physical/__init__.py +1 -0
  19. kodit/domain/enrichments/architecture/physical/discovery_notes.py +14 -0
  20. kodit/domain/enrichments/architecture/physical/formatter.py +11 -0
  21. kodit/domain/enrichments/architecture/physical/physical.py +17 -0
  22. kodit/domain/enrichments/development/__init__.py +1 -0
  23. kodit/domain/enrichments/development/development.py +18 -0
  24. kodit/domain/enrichments/development/snippet/__init__.py +1 -0
  25. kodit/domain/enrichments/development/snippet/snippet.py +21 -0
  26. kodit/domain/enrichments/enricher.py +17 -0
  27. kodit/domain/enrichments/enrichment.py +39 -0
  28. kodit/domain/enrichments/request.py +12 -0
  29. kodit/domain/enrichments/response.py +11 -0
  30. kodit/domain/enrichments/usage/__init__.py +1 -0
  31. kodit/domain/enrichments/usage/api_docs.py +19 -0
  32. kodit/domain/enrichments/usage/usage.py +18 -0
  33. kodit/domain/{entities.py → entities/__init__.py} +50 -195
  34. kodit/domain/entities/git.py +190 -0
  35. kodit/domain/factories/__init__.py +1 -0
  36. kodit/domain/factories/git_repo_factory.py +76 -0
  37. kodit/domain/protocols.py +264 -64
  38. kodit/domain/services/bm25_service.py +5 -1
  39. kodit/domain/services/embedding_service.py +3 -0
  40. kodit/domain/services/enrichment_service.py +9 -30
  41. kodit/domain/services/git_repository_service.py +429 -0
  42. kodit/domain/services/git_service.py +300 -0
  43. kodit/domain/services/physical_architecture_service.py +182 -0
  44. kodit/domain/services/task_status_query_service.py +2 -2
  45. kodit/domain/value_objects.py +87 -135
  46. kodit/infrastructure/api/client/__init__.py +0 -2
  47. kodit/infrastructure/api/v1/__init__.py +0 -4
  48. kodit/infrastructure/api/v1/dependencies.py +92 -46
  49. kodit/infrastructure/api/v1/routers/__init__.py +0 -6
  50. kodit/infrastructure/api/v1/routers/commits.py +352 -0
  51. kodit/infrastructure/api/v1/routers/queue.py +2 -2
  52. kodit/infrastructure/api/v1/routers/repositories.py +282 -0
  53. kodit/infrastructure/api/v1/routers/search.py +31 -14
  54. kodit/infrastructure/api/v1/schemas/__init__.py +0 -24
  55. kodit/infrastructure/api/v1/schemas/commit.py +96 -0
  56. kodit/infrastructure/api/v1/schemas/context.py +2 -0
  57. kodit/infrastructure/api/v1/schemas/enrichment.py +29 -0
  58. kodit/infrastructure/api/v1/schemas/repository.py +128 -0
  59. kodit/infrastructure/api/v1/schemas/search.py +12 -9
  60. kodit/infrastructure/api/v1/schemas/snippet.py +58 -0
  61. kodit/infrastructure/api/v1/schemas/tag.py +31 -0
  62. kodit/infrastructure/api/v1/schemas/task_status.py +2 -0
  63. kodit/infrastructure/bm25/local_bm25_repository.py +16 -4
  64. kodit/infrastructure/bm25/vectorchord_bm25_repository.py +68 -52
  65. kodit/infrastructure/cloning/git/git_python_adaptor.py +534 -0
  66. kodit/infrastructure/cloning/git/working_copy.py +1 -1
  67. kodit/infrastructure/embedding/embedding_factory.py +3 -2
  68. kodit/infrastructure/embedding/local_vector_search_repository.py +1 -1
  69. kodit/infrastructure/embedding/vectorchord_vector_search_repository.py +111 -84
  70. kodit/infrastructure/enricher/__init__.py +1 -0
  71. kodit/infrastructure/enricher/enricher_factory.py +53 -0
  72. kodit/infrastructure/{enrichment/litellm_enrichment_provider.py → enricher/litellm_enricher.py} +36 -56
  73. kodit/infrastructure/{enrichment/local_enrichment_provider.py → enricher/local_enricher.py} +19 -24
  74. kodit/infrastructure/enricher/null_enricher.py +36 -0
  75. kodit/infrastructure/indexing/fusion_service.py +1 -1
  76. kodit/infrastructure/mappers/enrichment_mapper.py +83 -0
  77. kodit/infrastructure/mappers/git_mapper.py +193 -0
  78. kodit/infrastructure/mappers/snippet_mapper.py +104 -0
  79. kodit/infrastructure/mappers/task_mapper.py +5 -44
  80. kodit/infrastructure/physical_architecture/__init__.py +1 -0
  81. kodit/infrastructure/physical_architecture/detectors/__init__.py +1 -0
  82. kodit/infrastructure/physical_architecture/detectors/docker_compose_detector.py +336 -0
  83. kodit/infrastructure/physical_architecture/formatters/__init__.py +1 -0
  84. kodit/infrastructure/physical_architecture/formatters/narrative_formatter.py +149 -0
  85. kodit/infrastructure/reporting/log_progress.py +8 -5
  86. kodit/infrastructure/reporting/telemetry_progress.py +21 -0
  87. kodit/infrastructure/slicing/api_doc_extractor.py +836 -0
  88. kodit/infrastructure/slicing/ast_analyzer.py +1128 -0
  89. kodit/infrastructure/slicing/slicer.py +87 -421
  90. kodit/infrastructure/sqlalchemy/embedding_repository.py +43 -23
  91. kodit/infrastructure/sqlalchemy/enrichment_v2_repository.py +118 -0
  92. kodit/infrastructure/sqlalchemy/entities.py +402 -158
  93. kodit/infrastructure/sqlalchemy/git_branch_repository.py +274 -0
  94. kodit/infrastructure/sqlalchemy/git_commit_repository.py +346 -0
  95. kodit/infrastructure/sqlalchemy/git_repository.py +262 -0
  96. kodit/infrastructure/sqlalchemy/git_tag_repository.py +268 -0
  97. kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +479 -0
  98. kodit/infrastructure/sqlalchemy/task_repository.py +29 -23
  99. kodit/infrastructure/sqlalchemy/task_status_repository.py +24 -12
  100. kodit/infrastructure/sqlalchemy/unit_of_work.py +10 -14
  101. kodit/mcp.py +12 -30
  102. kodit/migrations/env.py +1 -0
  103. kodit/migrations/versions/04b80f802e0c_foreign_key_review.py +100 -0
  104. kodit/migrations/versions/19f8c7faf8b9_add_generic_enrichment_type.py +260 -0
  105. kodit/migrations/versions/7f15f878c3a1_add_new_git_entities.py +690 -0
  106. kodit/migrations/versions/f9e5ef5e688f_add_git_commits_number.py +43 -0
  107. kodit/py.typed +0 -0
  108. kodit/utils/dump_config.py +361 -0
  109. kodit/utils/dump_openapi.py +6 -4
  110. kodit/utils/path_utils.py +29 -0
  111. {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/METADATA +3 -3
  112. kodit-0.5.1.dist-info/RECORD +168 -0
  113. kodit/application/factories/code_indexing_factory.py +0 -195
  114. kodit/application/services/auto_indexing_service.py +0 -99
  115. kodit/application/services/code_indexing_application_service.py +0 -410
  116. kodit/domain/services/index_query_service.py +0 -70
  117. kodit/domain/services/index_service.py +0 -269
  118. kodit/infrastructure/api/client/index_client.py +0 -57
  119. kodit/infrastructure/api/v1/routers/indexes.py +0 -164
  120. kodit/infrastructure/api/v1/schemas/index.py +0 -101
  121. kodit/infrastructure/bm25/bm25_factory.py +0 -28
  122. kodit/infrastructure/cloning/__init__.py +0 -1
  123. kodit/infrastructure/cloning/metadata.py +0 -98
  124. kodit/infrastructure/enrichment/__init__.py +0 -1
  125. kodit/infrastructure/enrichment/enrichment_factory.py +0 -52
  126. kodit/infrastructure/enrichment/null_enrichment_provider.py +0 -19
  127. kodit/infrastructure/mappers/index_mapper.py +0 -345
  128. kodit/infrastructure/reporting/tdqm_progress.py +0 -38
  129. kodit/infrastructure/slicing/language_detection_service.py +0 -18
  130. kodit/infrastructure/sqlalchemy/index_repository.py +0 -646
  131. kodit-0.4.3.dist-info/RECORD +0 -125
  132. /kodit/infrastructure/{enrichment → enricher}/utils.py +0 -0
  133. {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/WHEEL +0 -0
  134. {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/entry_points.txt +0 -0
  135. {kodit-0.4.3.dist-info → kodit-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -2,12 +2,15 @@
2
2
 
3
3
  from datetime import UTC, datetime
4
4
  from enum import Enum
5
+ from pathlib import Path
5
6
  from typing import Any
6
7
 
7
- from git import Actor
8
8
  from sqlalchemy import (
9
9
  DateTime,
10
+ Float,
10
11
  ForeignKey,
12
+ ForeignKeyConstraint,
13
+ Index,
11
14
  Integer,
12
15
  String,
13
16
  TypeDecorator,
@@ -43,6 +46,25 @@ class TZDateTime(TypeDecorator):
43
46
  return value
44
47
 
45
48
 
49
+ class PathType(TypeDecorator):
50
+ """Path type that stores Path objects as strings."""
51
+
52
+ impl = String
53
+ cache_ok = True
54
+
55
+ def process_bind_param(self, value: Any, dialect: Any) -> Any: # noqa: ARG002
56
+ """Process bind param - convert Path to string."""
57
+ if value is not None:
58
+ return str(value)
59
+ return value
60
+
61
+ def process_result_value(self, value: Any, dialect: Any) -> Any: # noqa: ARG002
62
+ """Process result value - convert string to Path."""
63
+ if value is not None:
64
+ return Path(value)
65
+ return value
66
+
67
+
46
68
  class Base(AsyncAttrs, DeclarativeBase):
47
69
  """Base class for all models."""
48
70
 
@@ -62,114 +84,6 @@ class CommonMixin:
62
84
  )
63
85
 
64
86
 
65
- class SourceType(Enum):
66
- """The type of source."""
67
-
68
- UNKNOWN = 0
69
- FOLDER = 1
70
- GIT = 2
71
-
72
-
73
- class Source(Base, CommonMixin):
74
- """Base model for tracking code sources.
75
-
76
- This model serves as the parent table for different types of sources.
77
- It provides common fields and relationships for all source types.
78
-
79
- Attributes:
80
- id: The unique identifier for the source.
81
- created_at: Timestamp when the source was created.
82
- updated_at: Timestamp when the source was last updated.
83
- cloned_uri: A URI to a copy of the source on the local filesystem.
84
- uri: The URI of the source.
85
-
86
- """
87
-
88
- __tablename__ = "sources"
89
- uri: Mapped[str] = mapped_column(String(1024), index=True, unique=True)
90
- cloned_path: Mapped[str] = mapped_column(String(1024), index=True)
91
- type: Mapped[SourceType] = mapped_column(
92
- SQLAlchemyEnum(SourceType), default=SourceType.UNKNOWN, index=True
93
- )
94
-
95
- def __init__(self, uri: str, cloned_path: str, source_type: SourceType) -> None:
96
- """Initialize a new Source instance for typing purposes."""
97
- super().__init__()
98
- self.uri = uri
99
- self.cloned_path = cloned_path
100
- self.type = source_type
101
-
102
-
103
- class Author(Base, CommonMixin):
104
- """Author model."""
105
-
106
- __tablename__ = "authors"
107
-
108
- __table_args__ = (UniqueConstraint("name", "email", name="uix_author"),)
109
-
110
- name: Mapped[str] = mapped_column(String(255), index=True)
111
- email: Mapped[str] = mapped_column(String(255), index=True)
112
-
113
- @staticmethod
114
- def from_actor(actor: Actor) -> "Author":
115
- """Create an Author from an Actor."""
116
- return Author(name=actor.name, email=actor.email)
117
-
118
-
119
- class AuthorFileMapping(Base, CommonMixin):
120
- """Author file mapping model."""
121
-
122
- __tablename__ = "author_file_mappings"
123
-
124
- __table_args__ = (
125
- UniqueConstraint("author_id", "file_id", name="uix_author_file_mapping"),
126
- )
127
-
128
- author_id: Mapped[int] = mapped_column(ForeignKey("authors.id"), index=True)
129
- file_id: Mapped[int] = mapped_column(ForeignKey("files.id"), index=True)
130
-
131
-
132
- class File(Base, CommonMixin):
133
- """File model."""
134
-
135
- __tablename__ = "files"
136
-
137
- source_id: Mapped[int] = mapped_column(ForeignKey("sources.id"))
138
- mime_type: Mapped[str] = mapped_column(String(255), default="", index=True)
139
- uri: Mapped[str] = mapped_column(String(1024), default="", index=True)
140
- cloned_path: Mapped[str] = mapped_column(String(1024), index=True)
141
- sha256: Mapped[str] = mapped_column(String(64), default="", index=True)
142
- size_bytes: Mapped[int] = mapped_column(Integer, default=0)
143
- extension: Mapped[str] = mapped_column(String(255), default="", index=True)
144
- file_processing_status: Mapped[int] = mapped_column(Integer, default=0)
145
-
146
- def __init__( # noqa: PLR0913
147
- self,
148
- created_at: datetime,
149
- updated_at: datetime,
150
- source_id: int,
151
- mime_type: str,
152
- uri: str,
153
- cloned_path: str,
154
- sha256: str,
155
- size_bytes: int,
156
- extension: str,
157
- file_processing_status: int,
158
- ) -> None:
159
- """Initialize a new File instance for typing purposes."""
160
- super().__init__()
161
- self.created_at = created_at
162
- self.updated_at = updated_at
163
- self.source_id = source_id
164
- self.mime_type = mime_type
165
- self.uri = uri
166
- self.cloned_path = cloned_path
167
- self.sha256 = sha256
168
- self.size_bytes = size_bytes
169
- self.extension = extension
170
- self.file_processing_status = file_processing_status
171
-
172
-
173
87
  class EmbeddingType(Enum):
174
88
  """Embedding type."""
175
89
 
@@ -182,59 +96,13 @@ class Embedding(Base, CommonMixin):
182
96
 
183
97
  __tablename__ = "embeddings"
184
98
 
185
- snippet_id: Mapped[int] = mapped_column(ForeignKey("snippets.id"), index=True)
99
+ snippet_id: Mapped[str] = mapped_column(String(64), index=True)
186
100
  type: Mapped[EmbeddingType] = mapped_column(
187
101
  SQLAlchemyEnum(EmbeddingType), index=True
188
102
  )
189
103
  embedding: Mapped[list[float]] = mapped_column(JSON)
190
104
 
191
105
 
192
- class Index(Base, CommonMixin):
193
- """Index model."""
194
-
195
- __tablename__ = "indexes"
196
-
197
- source_id: Mapped[int] = mapped_column(
198
- ForeignKey("sources.id"), unique=True, index=True
199
- )
200
-
201
- def __init__(self, source_id: int) -> None:
202
- """Initialize the index."""
203
- super().__init__()
204
- self.source_id = source_id
205
-
206
-
207
- class Snippet(Base, CommonMixin):
208
- """Snippet model."""
209
-
210
- __tablename__ = "snippets"
211
-
212
- file_id: Mapped[int] = mapped_column(ForeignKey("files.id"), index=True)
213
- index_id: Mapped[int] = mapped_column(ForeignKey("indexes.id"), index=True)
214
- content: Mapped[str] = mapped_column(UnicodeText, default="")
215
- summary: Mapped[str] = mapped_column(UnicodeText, default="")
216
-
217
- def __init__(
218
- self,
219
- file_id: int,
220
- index_id: int,
221
- content: str,
222
- summary: str = "",
223
- ) -> None:
224
- """Initialize the snippet."""
225
- super().__init__()
226
- self.file_id = file_id
227
- self.index_id = index_id
228
- self.content = content
229
- self.summary = summary
230
-
231
-
232
- class TaskType(Enum):
233
- """Task type."""
234
-
235
- INDEX_UPDATE = 1
236
-
237
-
238
106
  class Task(Base, CommonMixin):
239
107
  """Queued tasks."""
240
108
 
@@ -243,7 +111,7 @@ class Task(Base, CommonMixin):
243
111
  # dedup_key is used to deduplicate items in the queue
244
112
  dedup_key: Mapped[str] = mapped_column(String(255), index=True)
245
113
  # type represents what the task is meant to achieve
246
- type: Mapped[TaskType] = mapped_column(SQLAlchemyEnum(TaskType), index=True)
114
+ type: Mapped[str] = mapped_column(String(255), index=True)
247
115
  # payload contains the task-specific payload data
248
116
  payload: Mapped[dict] = mapped_column(JSON)
249
117
  # priority is used to determine the order of the items in the queue
@@ -252,7 +120,7 @@ class Task(Base, CommonMixin):
252
120
  def __init__(
253
121
  self,
254
122
  dedup_key: str,
255
- type: TaskType, # noqa: A002
123
+ type: str, # noqa: A002
256
124
  payload: dict,
257
125
  priority: int,
258
126
  ) -> None:
@@ -323,3 +191,379 @@ class TaskStatus(Base):
323
191
  self.total = total
324
192
  self.current = current
325
193
  self.message = message or ""
194
+
195
+
196
+ # Git-related entities for new GitRepo domain
197
+
198
+
199
+ class GitRepo(Base, CommonMixin):
200
+ """Git repository model."""
201
+
202
+ __tablename__ = "git_repos"
203
+
204
+ sanitized_remote_uri: Mapped[str] = mapped_column(
205
+ String(1024), index=True, unique=True
206
+ )
207
+ remote_uri: Mapped[str] = mapped_column(String(1024))
208
+ cloned_path: Mapped[Path | None] = mapped_column(PathType(1024), nullable=True)
209
+ last_scanned_at: Mapped[datetime | None] = mapped_column(TZDateTime, nullable=True)
210
+ num_commits: Mapped[int] = mapped_column(Integer, default=0)
211
+ num_branches: Mapped[int] = mapped_column(Integer, default=0)
212
+ num_tags: Mapped[int] = mapped_column(Integer, default=0)
213
+
214
+ def __init__( # noqa: PLR0913
215
+ self,
216
+ sanitized_remote_uri: str,
217
+ remote_uri: str,
218
+ cloned_path: Path | None,
219
+ last_scanned_at: datetime | None = None,
220
+ num_commits: int = 0,
221
+ num_branches: int = 0,
222
+ num_tags: int = 0,
223
+ ) -> None:
224
+ """Initialize Git repository."""
225
+ super().__init__()
226
+ self.sanitized_remote_uri = sanitized_remote_uri
227
+ self.remote_uri = remote_uri
228
+ self.cloned_path = cloned_path
229
+ self.last_scanned_at = last_scanned_at
230
+ self.num_commits = num_commits
231
+ self.num_branches = num_branches
232
+ self.num_tags = num_tags
233
+
234
+
235
+ class GitCommit(Base):
236
+ """Git commit model."""
237
+
238
+ __tablename__ = "git_commits"
239
+
240
+ commit_sha: Mapped[str] = mapped_column(String(64), primary_key=True)
241
+ created_at: Mapped[datetime] = mapped_column(
242
+ TZDateTime, nullable=False, default=lambda: datetime.now(UTC)
243
+ )
244
+ updated_at: Mapped[datetime] = mapped_column(
245
+ TZDateTime,
246
+ nullable=False,
247
+ default=lambda: datetime.now(UTC),
248
+ onupdate=lambda: datetime.now(UTC),
249
+ )
250
+ repo_id: Mapped[int] = mapped_column(ForeignKey("git_repos.id"), index=True)
251
+ date: Mapped[datetime] = mapped_column(TZDateTime)
252
+ message: Mapped[str] = mapped_column(UnicodeText)
253
+ parent_commit_sha: Mapped[str | None] = mapped_column(String(64), nullable=True)
254
+ author: Mapped[str] = mapped_column(String(255), index=True)
255
+
256
+ def __init__( # noqa: PLR0913
257
+ self,
258
+ commit_sha: str,
259
+ repo_id: int,
260
+ date: datetime,
261
+ message: str,
262
+ parent_commit_sha: str | None,
263
+ author: str,
264
+ ) -> None:
265
+ """Initialize Git commit."""
266
+ super().__init__()
267
+ self.commit_sha = commit_sha
268
+ self.repo_id = repo_id
269
+ self.date = date
270
+ self.message = message
271
+ self.parent_commit_sha = parent_commit_sha
272
+ self.author = author
273
+
274
+
275
+ class GitBranch(Base):
276
+ """Git branch model."""
277
+
278
+ __tablename__ = "git_branches"
279
+ repo_id: Mapped[int] = mapped_column(
280
+ ForeignKey("git_repos.id"), index=True, primary_key=True
281
+ )
282
+ name: Mapped[str] = mapped_column(String(255), index=True, primary_key=True)
283
+ created_at: Mapped[datetime] = mapped_column(
284
+ TZDateTime, nullable=False, default=lambda: datetime.now(UTC)
285
+ )
286
+ updated_at: Mapped[datetime] = mapped_column(
287
+ TZDateTime,
288
+ nullable=False,
289
+ default=lambda: datetime.now(UTC),
290
+ onupdate=lambda: datetime.now(UTC),
291
+ )
292
+ head_commit_sha: Mapped[str] = mapped_column(ForeignKey("git_commits.commit_sha"))
293
+
294
+ __table_args__ = (UniqueConstraint("repo_id", "name", name="uix_repo_branch"),)
295
+
296
+ def __init__(self, repo_id: int, name: str, head_commit_sha: str) -> None:
297
+ """Initialize Git branch."""
298
+ super().__init__()
299
+ self.repo_id = repo_id
300
+ self.name = name
301
+ self.head_commit_sha = head_commit_sha
302
+
303
+
304
+ class GitTrackingBranch(Base):
305
+ """Git tracking branch model."""
306
+
307
+ __tablename__ = "git_tracking_branches"
308
+ repo_id: Mapped[int] = mapped_column(
309
+ ForeignKey("git_repos.id"), index=True, primary_key=True
310
+ )
311
+ name: Mapped[str] = mapped_column(String(255), index=True, primary_key=True)
312
+ created_at: Mapped[datetime] = mapped_column(
313
+ TZDateTime, nullable=False, default=lambda: datetime.now(UTC)
314
+ )
315
+ updated_at: Mapped[datetime] = mapped_column(
316
+ TZDateTime,
317
+ nullable=False,
318
+ default=lambda: datetime.now(UTC),
319
+ onupdate=lambda: datetime.now(UTC),
320
+ )
321
+
322
+ def __init__(self, repo_id: int, name: str) -> None:
323
+ """Initialize Git tracking branch."""
324
+ super().__init__()
325
+ self.repo_id = repo_id
326
+ self.name = name
327
+
328
+
329
+ class GitTag(Base):
330
+ """Git tag model."""
331
+
332
+ __tablename__ = "git_tags"
333
+ repo_id: Mapped[int] = mapped_column(
334
+ ForeignKey("git_repos.id"), index=True, primary_key=True
335
+ )
336
+ name: Mapped[str] = mapped_column(String(255), index=True, primary_key=True)
337
+ created_at: Mapped[datetime] = mapped_column(
338
+ TZDateTime, nullable=False, default=lambda: datetime.now(UTC)
339
+ )
340
+ updated_at: Mapped[datetime] = mapped_column(
341
+ TZDateTime,
342
+ nullable=False,
343
+ default=lambda: datetime.now(UTC),
344
+ onupdate=lambda: datetime.now(UTC),
345
+ )
346
+ target_commit_sha: Mapped[str] = mapped_column(
347
+ ForeignKey("git_commits.commit_sha"), index=True
348
+ )
349
+
350
+ __table_args__ = (UniqueConstraint("repo_id", "name", name="uix_repo_tag"),)
351
+
352
+ def __init__(self, repo_id: int, name: str, target_commit_sha: str) -> None:
353
+ """Initialize Git tag."""
354
+ super().__init__()
355
+ self.repo_id = repo_id
356
+ self.name = name
357
+ self.target_commit_sha = target_commit_sha
358
+
359
+
360
+ class GitCommitFile(Base):
361
+ """Files in a git commit (tree entries)."""
362
+
363
+ __tablename__ = "git_commit_files"
364
+
365
+ commit_sha: Mapped[str] = mapped_column(
366
+ ForeignKey("git_commits.commit_sha"), primary_key=True
367
+ )
368
+ path: Mapped[str] = mapped_column(String(1024), primary_key=True)
369
+ blob_sha: Mapped[str] = mapped_column(String(64), index=True)
370
+ mime_type: Mapped[str] = mapped_column(String(255), index=True)
371
+ extension: Mapped[str] = mapped_column(String(255), index=True)
372
+ size: Mapped[int] = mapped_column(Integer)
373
+ created_at: Mapped[datetime] = mapped_column(TZDateTime, nullable=False)
374
+
375
+ __table_args__ = (UniqueConstraint("commit_sha", "path", name="uix_commit_file"),)
376
+
377
+ def __init__( # noqa: PLR0913
378
+ self,
379
+ commit_sha: str,
380
+ path: str,
381
+ blob_sha: str,
382
+ mime_type: str,
383
+ extension: str,
384
+ size: int,
385
+ created_at: datetime,
386
+ ) -> None:
387
+ """Initialize Git commit file."""
388
+ super().__init__()
389
+ self.commit_sha = commit_sha
390
+ self.path = path
391
+ self.blob_sha = blob_sha
392
+ self.mime_type = mime_type
393
+ self.size = size
394
+ self.created_at = created_at
395
+ self.extension = extension
396
+
397
+
398
+ class SnippetV2(Base):
399
+ """SnippetV2 model for commit-based snippets."""
400
+
401
+ __tablename__ = "snippets_v2"
402
+
403
+ sha: Mapped[str] = mapped_column(String(64), primary_key=True)
404
+ created_at: Mapped[datetime] = mapped_column(
405
+ TZDateTime, nullable=False, default=lambda: datetime.now(UTC)
406
+ )
407
+ updated_at: Mapped[datetime] = mapped_column(
408
+ TZDateTime,
409
+ nullable=False,
410
+ default=lambda: datetime.now(UTC),
411
+ onupdate=lambda: datetime.now(UTC),
412
+ )
413
+ content: Mapped[str] = mapped_column(UnicodeText)
414
+ extension: Mapped[str] = mapped_column(String(255), index=True)
415
+
416
+ def __init__(
417
+ self,
418
+ sha: str,
419
+ content: str,
420
+ extension: str,
421
+ ) -> None:
422
+ """Initialize snippet."""
423
+ super().__init__()
424
+ self.sha = sha
425
+ self.content = content
426
+ self.extension = extension
427
+
428
+
429
+ class SnippetV2File(Base):
430
+ """Association between snippets and files."""
431
+
432
+ __tablename__ = "snippet_v2_files"
433
+
434
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
435
+ snippet_sha: Mapped[str] = mapped_column(ForeignKey("snippets_v2.sha"), index=True)
436
+ blob_sha: Mapped[str] = mapped_column(String(64), index=True)
437
+ commit_sha: Mapped[str] = mapped_column(String(64), index=True)
438
+ file_path: Mapped[str] = mapped_column(String(1024), index=True)
439
+
440
+ __table_args__ = (
441
+ ForeignKeyConstraint(
442
+ ["commit_sha", "file_path"],
443
+ ["git_commit_files.commit_sha", "git_commit_files.path"],
444
+ ),
445
+ UniqueConstraint(
446
+ "snippet_sha",
447
+ "blob_sha",
448
+ "commit_sha",
449
+ "file_path",
450
+ name="uix_snippet_file",
451
+ ),
452
+ )
453
+
454
+ def __init__(
455
+ self, snippet_sha: str, blob_sha: str, commit_sha: str, file_path: str
456
+ ) -> None:
457
+ """Initialize snippet file association."""
458
+ super().__init__()
459
+ self.snippet_sha = snippet_sha
460
+ self.blob_sha = blob_sha
461
+ self.commit_sha = commit_sha
462
+ self.file_path = file_path
463
+
464
+
465
+ class CommitSnippetV2(Base):
466
+ """Association table for commits and snippets v2."""
467
+
468
+ __tablename__ = "commit_snippets_v2"
469
+
470
+ id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
471
+ commit_sha: Mapped[str] = mapped_column(
472
+ ForeignKey("git_commits.commit_sha"), index=True
473
+ )
474
+ snippet_sha: Mapped[str] = mapped_column(ForeignKey("snippets_v2.sha"), index=True)
475
+
476
+ __table_args__ = (
477
+ UniqueConstraint("commit_sha", "snippet_sha", name="uix_commit_snippet"),
478
+ )
479
+
480
+ def __init__(self, commit_sha: str, snippet_sha: str) -> None:
481
+ """Initialize commit snippet association."""
482
+ super().__init__()
483
+ self.commit_sha = commit_sha
484
+ self.snippet_sha = snippet_sha
485
+
486
+
487
+ class CommitIndex(Base):
488
+ """Commit index model."""
489
+
490
+ __tablename__ = "commit_indexes"
491
+
492
+ created_at: Mapped[datetime] = mapped_column(
493
+ TZDateTime, nullable=False, default=lambda: datetime.now(UTC)
494
+ )
495
+ updated_at: Mapped[datetime] = mapped_column(
496
+ TZDateTime,
497
+ nullable=False,
498
+ default=lambda: datetime.now(UTC),
499
+ onupdate=lambda: datetime.now(UTC),
500
+ )
501
+ commit_sha: Mapped[str] = mapped_column(String(64), primary_key=True)
502
+ status: Mapped[str] = mapped_column(String(255), index=True)
503
+ indexed_at: Mapped[datetime | None] = mapped_column(TZDateTime, nullable=True)
504
+ error_message: Mapped[str | None] = mapped_column(UnicodeText, nullable=True)
505
+ files_processed: Mapped[int] = mapped_column(Integer, default=0)
506
+ processing_time_seconds: Mapped[float] = mapped_column(Float, default=0.0)
507
+
508
+ def __init__( # noqa: PLR0913
509
+ self,
510
+ commit_sha: str,
511
+ status: str,
512
+ indexed_at: datetime | None = None,
513
+ error_message: str | None = None,
514
+ files_processed: int = 0,
515
+ processing_time_seconds: float = 0.0,
516
+ ) -> None:
517
+ """Initialize commit index."""
518
+ super().__init__()
519
+ self.commit_sha = commit_sha
520
+ self.status = status
521
+ self.indexed_at = indexed_at
522
+ self.error_message = error_message
523
+ self.files_processed = files_processed
524
+ self.processing_time_seconds = processing_time_seconds
525
+
526
+
527
+ class EnrichmentV2(Base, CommonMixin):
528
+ """Generic enrichment entity."""
529
+
530
+ __tablename__ = "enrichments_v2"
531
+
532
+ type: Mapped[str] = mapped_column(String, nullable=False, index=True)
533
+ subtype: Mapped[str] = mapped_column(String, nullable=False, index=True)
534
+ content: Mapped[str] = mapped_column(UnicodeText, nullable=False)
535
+
536
+ __table_args__ = (Index("idx_type_subtype", "type", "subtype"),)
537
+
538
+
539
+ class EnrichmentAssociation(Base, CommonMixin):
540
+ """Polymorphic association between enrichments and entities."""
541
+
542
+ __tablename__ = "enrichment_associations"
543
+
544
+ enrichment_id: Mapped[int] = mapped_column(
545
+ ForeignKey("enrichments_v2.id", ondelete="CASCADE"),
546
+ nullable=False,
547
+ index=True,
548
+ )
549
+ entity_type: Mapped[str] = mapped_column(
550
+ String(50),
551
+ nullable=False,
552
+ index=True,
553
+ )
554
+ entity_id: Mapped[str] = mapped_column(
555
+ String(255),
556
+ nullable=False,
557
+ index=True,
558
+ )
559
+
560
+ __table_args__ = (
561
+ UniqueConstraint(
562
+ "entity_type",
563
+ "entity_id",
564
+ "enrichment_id",
565
+ name="uix_entity_enrichment",
566
+ ),
567
+ Index("idx_entity_lookup", "entity_type", "entity_id"),
568
+ {"sqlite_autoincrement": True},
569
+ )