kodit 0.5.4__py3-none-any.whl → 0.5.5__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 (54) hide show
  1. kodit/_version.py +2 -2
  2. kodit/application/factories/server_factory.py +54 -32
  3. kodit/application/services/code_search_application_service.py +89 -12
  4. kodit/application/services/commit_indexing_application_service.py +314 -195
  5. kodit/application/services/enrichment_query_service.py +274 -43
  6. kodit/application/services/indexing_worker_service.py +1 -1
  7. kodit/application/services/queue_service.py +15 -10
  8. kodit/application/services/sync_scheduler.py +2 -1
  9. kodit/domain/enrichments/architecture/architecture.py +1 -1
  10. kodit/domain/enrichments/architecture/physical/physical.py +1 -1
  11. kodit/domain/enrichments/development/development.py +1 -1
  12. kodit/domain/enrichments/development/snippet/snippet.py +12 -5
  13. kodit/domain/enrichments/enrichment.py +31 -4
  14. kodit/domain/enrichments/usage/api_docs.py +1 -1
  15. kodit/domain/enrichments/usage/usage.py +1 -1
  16. kodit/domain/entities/git.py +30 -25
  17. kodit/domain/factories/git_repo_factory.py +20 -5
  18. kodit/domain/protocols.py +56 -125
  19. kodit/domain/services/embedding_service.py +14 -16
  20. kodit/domain/services/git_repository_service.py +60 -38
  21. kodit/domain/services/git_service.py +18 -11
  22. kodit/domain/tracking/resolution_service.py +6 -16
  23. kodit/domain/value_objects.py +2 -9
  24. kodit/infrastructure/api/v1/dependencies.py +12 -3
  25. kodit/infrastructure/api/v1/query_params.py +27 -0
  26. kodit/infrastructure/api/v1/routers/commits.py +91 -85
  27. kodit/infrastructure/api/v1/routers/repositories.py +53 -37
  28. kodit/infrastructure/api/v1/routers/search.py +1 -1
  29. kodit/infrastructure/api/v1/schemas/enrichment.py +14 -0
  30. kodit/infrastructure/api/v1/schemas/repository.py +1 -1
  31. kodit/infrastructure/slicing/api_doc_extractor.py +0 -2
  32. kodit/infrastructure/sqlalchemy/embedding_repository.py +44 -34
  33. kodit/infrastructure/sqlalchemy/enrichment_association_repository.py +73 -0
  34. kodit/infrastructure/sqlalchemy/enrichment_v2_repository.py +116 -97
  35. kodit/infrastructure/sqlalchemy/entities.py +12 -116
  36. kodit/infrastructure/sqlalchemy/git_branch_repository.py +52 -244
  37. kodit/infrastructure/sqlalchemy/git_commit_repository.py +35 -324
  38. kodit/infrastructure/sqlalchemy/git_file_repository.py +70 -0
  39. kodit/infrastructure/sqlalchemy/git_repository.py +60 -230
  40. kodit/infrastructure/sqlalchemy/git_tag_repository.py +53 -240
  41. kodit/infrastructure/sqlalchemy/query.py +331 -0
  42. kodit/infrastructure/sqlalchemy/repository.py +203 -0
  43. kodit/infrastructure/sqlalchemy/task_repository.py +79 -58
  44. kodit/infrastructure/sqlalchemy/task_status_repository.py +45 -52
  45. kodit/migrations/versions/4b1a3b2c8fa5_refactor_git_tracking.py +190 -0
  46. {kodit-0.5.4.dist-info → kodit-0.5.5.dist-info}/METADATA +1 -1
  47. {kodit-0.5.4.dist-info → kodit-0.5.5.dist-info}/RECORD +50 -48
  48. kodit/infrastructure/mappers/enrichment_mapper.py +0 -83
  49. kodit/infrastructure/mappers/git_mapper.py +0 -193
  50. kodit/infrastructure/mappers/snippet_mapper.py +0 -104
  51. kodit/infrastructure/sqlalchemy/snippet_v2_repository.py +0 -479
  52. {kodit-0.5.4.dist-info → kodit-0.5.5.dist-info}/WHEEL +0 -0
  53. {kodit-0.5.4.dist-info → kodit-0.5.5.dist-info}/entry_points.txt +0 -0
  54. {kodit-0.5.4.dist-info → kodit-0.5.5.dist-info}/licenses/LICENSE +0 -0
@@ -2,47 +2,67 @@
2
2
 
3
3
  import structlog
4
4
 
5
- from kodit.domain.enrichments.enrichment import EnrichmentV2
5
+ from kodit.domain.enrichments.architecture.architecture import (
6
+ ENRICHMENT_TYPE_ARCHITECTURE,
7
+ )
8
+ from kodit.domain.enrichments.architecture.physical.physical import (
9
+ ENRICHMENT_SUBTYPE_PHYSICAL,
10
+ )
11
+ from kodit.domain.enrichments.development.development import ENRICHMENT_TYPE_DEVELOPMENT
12
+ from kodit.domain.enrichments.development.snippet.snippet import (
13
+ ENRICHMENT_SUBTYPE_SNIPPET,
14
+ ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY,
15
+ )
16
+ from kodit.domain.enrichments.enrichment import EnrichmentAssociation, EnrichmentV2
17
+ from kodit.domain.enrichments.usage.api_docs import ENRICHMENT_SUBTYPE_API_DOCS
18
+ from kodit.domain.enrichments.usage.usage import ENRICHMENT_TYPE_USAGE
19
+ from kodit.domain.protocols import (
20
+ EnrichmentAssociationRepository,
21
+ EnrichmentV2Repository,
22
+ )
6
23
  from kodit.domain.tracking.resolution_service import TrackableResolutionService
7
24
  from kodit.domain.tracking.trackable import Trackable
8
- from kodit.infrastructure.sqlalchemy.enrichment_v2_repository import (
9
- EnrichmentV2Repository,
25
+ from kodit.infrastructure.api.v1.query_params import PaginationParams
26
+ from kodit.infrastructure.sqlalchemy import entities as db_entities
27
+ from kodit.infrastructure.sqlalchemy.query import (
28
+ EnrichmentAssociationQueryBuilder,
29
+ EnrichmentQueryBuilder,
10
30
  )
11
31
 
12
32
 
13
33
  class EnrichmentQueryService:
14
- """Finds the latest commit with enrichments for a trackable.
15
-
16
- Orchestrates domain services and repositories to fulfill the use case.
17
- """
34
+ """Service for querying enrichments."""
18
35
 
19
36
  def __init__(
20
37
  self,
21
38
  trackable_resolution: TrackableResolutionService,
22
39
  enrichment_repo: EnrichmentV2Repository,
40
+ enrichment_association_repository: EnrichmentAssociationRepository,
23
41
  ) -> None:
24
42
  """Initialize the enrichment query service."""
25
43
  self.trackable_resolution = trackable_resolution
26
44
  self.enrichment_repo = enrichment_repo
45
+ self.enrichment_association_repository = enrichment_association_repository
27
46
  self.log = structlog.get_logger(__name__)
28
47
 
48
+ async def associations_for_commit(
49
+ self, commit_sha: str
50
+ ) -> list[EnrichmentAssociation]:
51
+ """Get enrichments for a commit."""
52
+ return await self.enrichment_association_repository.find(
53
+ EnrichmentAssociationQueryBuilder.for_enrichment_associations(
54
+ entity_type=db_entities.GitCommit.__tablename__,
55
+ entity_ids=[commit_sha],
56
+ )
57
+ )
58
+
29
59
  async def find_latest_enriched_commit(
30
60
  self,
31
61
  trackable: Trackable,
32
62
  enrichment_type: str | None = None,
33
63
  max_commits_to_check: int = 100,
34
64
  ) -> str | None:
35
- """Find the most recent commit with enrichments.
36
-
37
- Args:
38
- trackable: What to track (branch, tag, or commit)
39
- enrichment_type: Optional filter for specific enrichment type
40
- max_commits_to_check: How far back in history to search
41
-
42
- Returns:
43
- Commit SHA of the most recent commit with enrichments, or None
44
-
45
- """
65
+ """Find the most recent commit with enrichments."""
46
66
  # Get candidate commits from the trackable
47
67
  candidate_commits = await self.trackable_resolution.resolve_to_commits(
48
68
  trackable, max_commits_to_check
@@ -52,44 +72,255 @@ class EnrichmentQueryService:
52
72
  return None
53
73
 
54
74
  # Check which commits have enrichments
55
- enrichments = await self.enrichment_repo.enrichments_for_entity_type(
56
- entity_type="git_commit",
57
- entity_ids=candidate_commits,
58
- )
59
-
60
- # Filter by type if specified
61
- if enrichment_type:
62
- enrichments = [e for e in enrichments if e.type == enrichment_type]
63
-
64
- # Find the first commit (newest) that has enrichments
65
75
  for commit_sha in candidate_commits:
66
- if any(e.entity_id == commit_sha for e in enrichments):
76
+ enrichments = await self.all_enrichments_for_commit(
77
+ commit_sha=commit_sha,
78
+ pagination=PaginationParams(page_size=1),
79
+ enrichment_type=enrichment_type,
80
+ )
81
+ if enrichments:
67
82
  return commit_sha
68
83
 
69
84
  return None
70
85
 
86
+ async def all_enrichments_for_commit(
87
+ self,
88
+ commit_sha: str,
89
+ pagination: PaginationParams,
90
+ enrichment_type: str | None = None,
91
+ enrichment_subtype: str | None = None,
92
+ ) -> dict[EnrichmentV2, list[EnrichmentAssociation]]:
93
+ """Get all enrichments for a specific commit."""
94
+ associations = await self.enrichment_association_repository.find(
95
+ EnrichmentAssociationQueryBuilder().for_commit(commit_sha)
96
+ )
97
+ enrichment_ids = [association.enrichment_id for association in associations]
98
+ query = EnrichmentQueryBuilder().for_ids(enrichment_ids).paginate(pagination)
99
+ if enrichment_type:
100
+ query = query.for_type(enrichment_type)
101
+ if enrichment_subtype:
102
+ query = query.for_subtype(enrichment_subtype)
103
+ enrichments = await self.enrichment_repo.find(query)
104
+ # Find all other associations for these enrichments
105
+ other_associations = await self.enrichment_association_repository.find(
106
+ EnrichmentAssociationQueryBuilder().for_enrichments(enrichments)
107
+ )
108
+ all_associations = set(associations + other_associations)
109
+ return {
110
+ enrichment: [
111
+ association
112
+ for association in all_associations
113
+ if association.enrichment_id == enrichment.id
114
+ or association.entity_id == str(enrichment.id)
115
+ ]
116
+ for enrichment in enrichments
117
+ }
118
+
119
+ async def get_all_snippets_for_commit(self, commit_sha: str) -> list[EnrichmentV2]:
120
+ """Get snippet enrichments for a commit."""
121
+ return list(
122
+ await self.all_enrichments_for_commit(
123
+ commit_sha=commit_sha,
124
+ pagination=PaginationParams(page_size=32000),
125
+ enrichment_type=ENRICHMENT_TYPE_DEVELOPMENT,
126
+ enrichment_subtype=ENRICHMENT_SUBTYPE_SNIPPET,
127
+ )
128
+ )
129
+
130
+ async def get_summaries_for_commit(self, commit_sha: str) -> list[EnrichmentV2]:
131
+ """Get summary enrichments for a commit."""
132
+ return await self.get_enrichments_for_commit(
133
+ commit_sha,
134
+ enrichment_type=ENRICHMENT_TYPE_DEVELOPMENT,
135
+ enrichment_subtype=ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY,
136
+ )
137
+
71
138
  async def get_enrichments_for_commit(
72
139
  self,
73
140
  commit_sha: str,
74
141
  enrichment_type: str | None = None,
142
+ enrichment_subtype: str | None = None,
75
143
  ) -> list[EnrichmentV2]:
76
- """Get all enrichments for a specific commit.
144
+ """Get enrichments for a commit."""
145
+ # Find associations pointing to this commit
146
+ all_associations = await self.enrichment_association_repository.find(
147
+ EnrichmentAssociationQueryBuilder().for_commit(commit_sha)
148
+ )
149
+ query = EnrichmentQueryBuilder().for_ids(
150
+ enrichment_ids=[
151
+ int(association.enrichment_id) for association in all_associations
152
+ ]
153
+ )
154
+ if enrichment_type:
155
+ query = query.for_type(enrichment_type)
156
+ if enrichment_subtype:
157
+ query = query.for_subtype(enrichment_subtype)
158
+ return await self.enrichment_repo.find(query)
77
159
 
78
- Args:
79
- commit_sha: The commit SHA to get enrichments for
80
- enrichment_type: Optional filter for specific enrichment type
160
+ async def get_architecture_docs_for_commit(
161
+ self, commit_sha: str
162
+ ) -> list[EnrichmentV2]:
163
+ """Get architecture documentation enrichments for a commit."""
164
+ return await self.get_enrichments_for_commit(
165
+ commit_sha,
166
+ enrichment_type=ENRICHMENT_TYPE_ARCHITECTURE,
167
+ enrichment_subtype=ENRICHMENT_SUBTYPE_PHYSICAL,
168
+ )
81
169
 
82
- Returns:
83
- List of enrichments for the commit
170
+ async def get_api_docs_for_commit(self, commit_sha: str) -> list[EnrichmentV2]:
171
+ """Get API documentation enrichments for a commit."""
172
+ return await self.get_enrichments_for_commit(
173
+ commit_sha,
174
+ enrichment_type=ENRICHMENT_TYPE_USAGE,
175
+ enrichment_subtype=ENRICHMENT_SUBTYPE_API_DOCS,
176
+ )
84
177
 
85
- """
86
- enrichments = await self.enrichment_repo.enrichments_for_entity_type(
87
- entity_type="git_commit",
88
- entity_ids=[commit_sha],
178
+ async def get_enrichment_entities_from_associations(
179
+ self, associations: list[EnrichmentAssociation]
180
+ ) -> list[EnrichmentV2]:
181
+ """Get enrichments by their associations."""
182
+ return await self.enrichment_repo.find(
183
+ EnrichmentQueryBuilder().for_ids(
184
+ enrichment_ids=[
185
+ int(association.entity_id) for association in associations
186
+ ]
187
+ )
89
188
  )
90
189
 
91
- # Filter by type if specified
92
- if enrichment_type:
93
- enrichments = [e for e in enrichments if e.type == enrichment_type]
190
+ async def get_enrichments_by_ids(
191
+ self, enrichment_ids: list[int]
192
+ ) -> list[EnrichmentV2]:
193
+ """Get enrichments by their IDs."""
194
+ return await self.enrichment_repo.find(
195
+ EnrichmentQueryBuilder().for_ids(enrichment_ids=enrichment_ids)
196
+ )
197
+
198
+ async def has_snippets_for_commit(self, commit_sha: str) -> bool:
199
+ """Check if a commit has snippet enrichments."""
200
+ snippets = await self.get_all_snippets_for_commit(commit_sha)
201
+ return len(snippets) > 0
202
+
203
+ async def has_summaries_for_commit(self, commit_sha: str) -> bool:
204
+ """Check if a commit has summary enrichments."""
205
+ summaries = await self.get_summaries_for_commit(commit_sha)
206
+ return len(summaries) > 0
207
+
208
+ async def has_architecture_for_commit(self, commit_sha: str) -> bool:
209
+ """Check if a commit has architecture enrichments."""
210
+ architecture_docs = await self.get_architecture_docs_for_commit(commit_sha)
211
+ return len(architecture_docs) > 0
212
+
213
+ async def has_api_docs_for_commit(self, commit_sha: str) -> bool:
214
+ """Check if a commit has API documentation enrichments."""
215
+ api_docs = await self.get_api_docs_for_commit(commit_sha)
216
+ return len(api_docs) > 0
217
+
218
+ async def associations_for_enrichments(
219
+ self, enrichments: list[EnrichmentV2]
220
+ ) -> list[EnrichmentAssociation]:
221
+ """Get enrichment associations for given enrichment IDs."""
222
+ return await self.enrichment_association_repository.find(
223
+ EnrichmentAssociationQueryBuilder()
224
+ .for_enrichments(enrichments)
225
+ .for_enrichment_type()
226
+ )
227
+
228
+ async def snippet_associations_from_enrichments(
229
+ self, enrichments: list[EnrichmentV2]
230
+ ) -> list[EnrichmentAssociation]:
231
+ """Get snippet enrichment associations for given enrichments."""
232
+ return await self.enrichment_association_repository.find(
233
+ EnrichmentAssociationQueryBuilder()
234
+ .for_enrichments(enrichments)
235
+ .for_enrichment_type()
236
+ )
237
+
238
+ async def snippets_for_summary_enrichments(
239
+ self, summary_enrichments: list[EnrichmentV2]
240
+ ) -> list[EnrichmentV2]:
241
+ """Get snippet enrichment IDs for summary enrichments, preserving order."""
242
+ if not summary_enrichments:
243
+ return []
244
+
245
+ # Get associations where enrichment_id points to these summaries
246
+ associations = await self.enrichment_association_repository.find(
247
+ EnrichmentAssociationQueryBuilder()
248
+ .for_enrichments(summary_enrichments)
249
+ .for_enrichment_type()
250
+ )
251
+
252
+ all_enrichments = await self.enrichment_repo.find(
253
+ EnrichmentQueryBuilder().for_ids(
254
+ enrichment_ids=[
255
+ int(association.entity_id) for association in associations
256
+ ]
257
+ )
258
+ )
259
+ snippet_enrichments = [
260
+ e for e in all_enrichments if e.subtype == ENRICHMENT_SUBTYPE_SNIPPET
261
+ ]
262
+
263
+ # Re-Sort snippet enrichments to be in the same order as the associations
264
+ original_snippet_ids = [association.entity_id for association in associations]
265
+ return sorted(
266
+ snippet_enrichments,
267
+ key=lambda x: original_snippet_ids.index(str(x.id)),
268
+ )
269
+
270
+ async def get_enrichments_pointing_to_enrichments(
271
+ self, target_enrichment_ids: list[int]
272
+ ) -> dict[int, list[EnrichmentV2]]:
273
+ """Get enrichments that point to the given enrichments, grouped by target."""
274
+ # Get associations pointing to these enrichments
275
+ associations = await self.enrichment_association_repository.find(
276
+ EnrichmentAssociationQueryBuilder()
277
+ .for_enrichment_type()
278
+ .for_entity_ids(
279
+ [str(enrichment_id) for enrichment_id in target_enrichment_ids]
280
+ )
281
+ )
282
+
283
+ if not associations:
284
+ return {eid: [] for eid in target_enrichment_ids}
285
+
286
+ # Get the enrichments referenced by these associations
287
+ enrichment_ids = [a.enrichment_id for a in associations]
288
+ enrichments = await self.enrichment_repo.find(
289
+ EnrichmentQueryBuilder().for_ids(enrichment_ids=enrichment_ids)
290
+ )
291
+
292
+ # Create lookup map
293
+ enrichment_map = {e.id: e for e in enrichments if e.id is not None}
294
+
295
+ # Group by target enrichment ID
296
+ result: dict[int, list[EnrichmentV2]] = {
297
+ eid: [] for eid in target_enrichment_ids
298
+ }
299
+ for association in associations:
300
+ target_id = int(association.entity_id)
301
+ if target_id in result and association.enrichment_id in enrichment_map:
302
+ result[target_id].append(enrichment_map[association.enrichment_id])
303
+
304
+ return result
305
+
306
+ async def summary_to_snippet_map(self, summary_ids: list[int]) -> dict[int, int]:
307
+ """Get a map of summary IDs to snippet IDs."""
308
+ # Get the snippet enrichment IDs that these summaries point to
309
+ summary_enrichments = await self.get_enrichments_by_ids(summary_ids)
310
+
311
+ # Get all the associations for these summary enrichments
312
+ all_associations = await self.associations_for_enrichments(summary_enrichments)
313
+
314
+ # Get all enrichments for these summary associations
315
+ all_snippet_enrichments = await self.get_enrichment_entities_from_associations(
316
+ all_associations
317
+ )
318
+ snippet_type_map = {e.id: e.subtype for e in all_snippet_enrichments}
94
319
 
95
- return enrichments
320
+ # Create a lookup map from summary ID to snippet ID, via the associations,
321
+ # filtering out any snippets that are not summary enrichments
322
+ return {
323
+ assoc.enrichment_id: int(assoc.entity_id)
324
+ for assoc in all_associations
325
+ if snippet_type_map[int(assoc.entity_id)] == ENRICHMENT_SUBTYPE_SNIPPET
326
+ }
@@ -75,7 +75,7 @@ class IndexingWorkerService:
75
75
  if task:
76
76
  await self._process_task(task)
77
77
  # Only remove the task if it was processed successfully
78
- await self.task_repository.remove(task)
78
+ await self.task_repository.delete(task)
79
79
  continue
80
80
 
81
81
  # If no task, sleep for a bit
@@ -8,6 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
8
8
 
9
9
  from kodit.domain.entities import Task
10
10
  from kodit.domain.value_objects import QueuePriority, TaskOperation
11
+ from kodit.infrastructure.sqlalchemy.query import FilterOperator, QueryBuilder
11
12
  from kodit.infrastructure.sqlalchemy.task_repository import (
12
13
  create_task_repository,
13
14
  )
@@ -30,16 +31,12 @@ class QueueService:
30
31
 
31
32
  async def enqueue_task(self, task: Task) -> None:
32
33
  """Queue a task in the database."""
33
- # See if task already exists
34
- db_task = await self.task_repository.get(task.id)
35
- if db_task:
36
- # Task already exists, update priority
37
- db_task.priority = task.priority
38
- await self.task_repository.update(db_task)
34
+ # Check if task already exists
35
+ exists = await self.task_repository.exists(task.id)
36
+ await self.task_repository.save(task)
37
+ if exists:
39
38
  self.log.info("Task updated", task_id=task.id, task_type=task.type)
40
39
  else:
41
- # Otherwise, add task
42
- await self.task_repository.add(task)
43
40
  self.log.info(
44
41
  "Task queued",
45
42
  task_id=task.id,
@@ -69,8 +66,16 @@ class QueueService:
69
66
  self, task_operation: TaskOperation | None = None
70
67
  ) -> list[Task]:
71
68
  """List all tasks in the queue."""
72
- return await self.task_repository.list(task_operation)
69
+ query = QueryBuilder()
70
+ if task_operation:
71
+ query.filter("type", FilterOperator.EQ, task_operation.value)
72
+ query.sort("priority", descending=True)
73
+ query.sort("created_at", descending=True)
74
+ return await self.task_repository.find(query)
73
75
 
74
76
  async def get_task(self, task_id: str) -> Task | None:
75
77
  """Get a specific task by ID."""
76
- return await self.task_repository.get(task_id)
78
+ try:
79
+ return await self.task_repository.get(task_id)
80
+ except ValueError:
81
+ return None
@@ -11,6 +11,7 @@ from kodit.domain.value_objects import (
11
11
  PrescribedOperations,
12
12
  QueuePriority,
13
13
  )
14
+ from kodit.infrastructure.sqlalchemy.query import QueryBuilder
14
15
 
15
16
 
16
17
  class SyncSchedulerService:
@@ -68,7 +69,7 @@ class SyncSchedulerService:
68
69
  self.log.info("Starting sync operation")
69
70
 
70
71
  # Sync each index - queue all 5 tasks with priority ordering
71
- for repo in await self.repo_repository.get_all():
72
+ for repo in await self.repo_repository.find(QueryBuilder()):
72
73
  await self.queue_service.enqueue_tasks(
73
74
  tasks=PrescribedOperations.SYNC_REPOSITORY,
74
75
  base_priority=QueuePriority.BACKGROUND,
@@ -10,7 +10,7 @@ from kodit.domain.enrichments.enrichment import (
10
10
  ENRICHMENT_TYPE_ARCHITECTURE = "architecture"
11
11
 
12
12
 
13
- @dataclass
13
+ @dataclass(frozen=True)
14
14
  class ArchitectureEnrichment(CommitEnrichment, ABC):
15
15
  """Enrichment containing physical architecture discovery for a commit."""
16
16
 
@@ -7,7 +7,7 @@ from kodit.domain.enrichments.architecture.architecture import ArchitectureEnric
7
7
  ENRICHMENT_SUBTYPE_PHYSICAL = "physical"
8
8
 
9
9
 
10
- @dataclass
10
+ @dataclass(frozen=True)
11
11
  class PhysicalArchitectureEnrichment(ArchitectureEnrichment):
12
12
  """Enrichment containing physical architecture discovery for a commit."""
13
13
 
@@ -8,7 +8,7 @@ from kodit.domain.enrichments.enrichment import CommitEnrichment
8
8
  ENRICHMENT_TYPE_DEVELOPMENT = "development"
9
9
 
10
10
 
11
- @dataclass
11
+ @dataclass(frozen=True)
12
12
  class DevelopmentEnrichment(CommitEnrichment, ABC):
13
13
  """Enrichment containing development discovery for a commit."""
14
14
 
@@ -5,10 +5,11 @@ from dataclasses import dataclass
5
5
  from kodit.domain.enrichments.development.development import DevelopmentEnrichment
6
6
 
7
7
  ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY = "snippet_summary"
8
+ ENRICHMENT_SUBTYPE_SNIPPET = "snippet"
8
9
 
9
10
 
10
- @dataclass
11
- class SnippetEnrichment(DevelopmentEnrichment):
11
+ @dataclass(frozen=True)
12
+ class SnippetEnrichmentSummary(DevelopmentEnrichment):
12
13
  """Enrichment specific to code snippets."""
13
14
 
14
15
  @property
@@ -16,6 +17,12 @@ class SnippetEnrichment(DevelopmentEnrichment):
16
17
  """Return the enrichment subtype."""
17
18
  return ENRICHMENT_SUBTYPE_SNIPPET_SUMMARY
18
19
 
19
- def entity_type_key(self) -> str:
20
- """Return the entity type key this enrichment is for."""
21
- return "snippet_v2"
20
+
21
+ @dataclass(frozen=True)
22
+ class SnippetEnrichment(DevelopmentEnrichment):
23
+ """Enrichment specific to code snippets."""
24
+
25
+ @property
26
+ def subtype(self) -> str | None:
27
+ """Return the enrichment subtype."""
28
+ return ENRICHMENT_SUBTYPE_SNIPPET
@@ -4,12 +4,14 @@ from abc import ABC, abstractmethod
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime
6
6
 
7
+ # TODO(Phil): db stuff should not exist in the domain layer, create a mapper for this.
8
+ from kodit.infrastructure.sqlalchemy import entities as db_entities
7
9
 
8
- @dataclass
10
+
11
+ @dataclass(frozen=True)
9
12
  class EnrichmentV2(ABC):
10
13
  """Generic enrichment that can be attached to any entity."""
11
14
 
12
- entity_id: str
13
15
  content: str = ""
14
16
  id: int | None = None
15
17
  created_at: datetime | None = None
@@ -30,10 +32,35 @@ class EnrichmentV2(ABC):
30
32
  """Return the entity type key this enrichment is for."""
31
33
 
32
34
 
33
- @dataclass
35
+ @dataclass(frozen=True)
34
36
  class CommitEnrichment(EnrichmentV2, ABC):
35
37
  """Enrichment specific to commits."""
36
38
 
37
39
  def entity_type_key(self) -> str:
38
40
  """Return the entity type key this enrichment is for."""
39
- return "git_commit"
41
+ return db_entities.GitCommit.__tablename__
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class EnrichmentAssociation:
46
+ """Association between an enrichment and an entity."""
47
+
48
+ enrichment_id: int
49
+ entity_id: str
50
+ entity_type: str
51
+ id: int | None = None
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class CommitEnrichmentAssociation(EnrichmentAssociation):
56
+ """Association between a commit and an enrichment."""
57
+
58
+ entity_type: str = db_entities.GitCommit.__tablename__
59
+
60
+
61
+ @dataclass
62
+ class SnippetSummaryAssociation:
63
+ """Association between a snippet and a summary enrichment."""
64
+
65
+ snippet_summary: EnrichmentAssociation
66
+ snippet: EnrichmentAssociation
@@ -7,7 +7,7 @@ from kodit.domain.enrichments.usage.usage import UsageEnrichment
7
7
  ENRICHMENT_SUBTYPE_API_DOCS = "api_docs"
8
8
 
9
9
 
10
- @dataclass
10
+ @dataclass(frozen=True)
11
11
  class APIDocEnrichment(UsageEnrichment):
12
12
  """API documentation enrichment for a module."""
13
13
 
@@ -8,7 +8,7 @@ from kodit.domain.enrichments.enrichment import CommitEnrichment
8
8
  ENRICHMENT_TYPE_USAGE = "usage"
9
9
 
10
10
 
11
- @dataclass
11
+ @dataclass(frozen=True)
12
12
  class UsageEnrichment(CommitEnrichment, ABC):
13
13
  """Enrichment containing development discovery for a commit."""
14
14