graph-dependency-analyzer 0.2.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.
- graph_dependency_analyzer-0.2.0.dist-info/METADATA +158 -0
- graph_dependency_analyzer-0.2.0.dist-info/RECORD +86 -0
- graph_dependency_analyzer-0.2.0.dist-info/WHEEL +5 -0
- graph_dependency_analyzer-0.2.0.dist-info/entry_points.txt +2 -0
- graph_dependency_analyzer-0.2.0.dist-info/top_level.txt +1 -0
- src/__init__.py +0 -0
- src/application/__init__.py +0 -0
- src/application/dtos/__init__.py +0 -0
- src/application/dtos/affected_component.py +59 -0
- src/application/dtos/batch_processing_result.py +17 -0
- src/application/dtos/impact_analysis_response.py +56 -0
- src/application/dtos/impact_query_request.py +38 -0
- src/application/dtos/index_batch_request.py +53 -0
- src/application/dtos/index_batch_response.py +72 -0
- src/application/dtos/index_result.py +20 -0
- src/application/dtos/repository_index_result.py +79 -0
- src/application/exceptions.py +41 -0
- src/application/services/__init__.py +0 -0
- src/application/services/file_scanner_service.py +258 -0
- src/application/services/progress_tracker.py +132 -0
- src/application/use_cases/__init__.py +0 -0
- src/application/use_cases/clear_all_data_use_case.py +180 -0
- src/application/use_cases/index_batch_use_case.py +153 -0
- src/application/use_cases/index_repository_use_case.py +683 -0
- src/application/use_cases/indexing_orchestrator.py +181 -0
- src/application/use_cases/list_repositories_use_case.py +84 -0
- src/cli.py +512 -0
- src/config/__init__.py +5 -0
- src/config/container.py +211 -0
- src/config/logging_config.py +123 -0
- src/config/settings.py +165 -0
- src/domain/__init__.py +0 -0
- src/domain/entities/__init__.py +20 -0
- src/domain/entities/ast_node.py +32 -0
- src/domain/entities/code_chunk.py +45 -0
- src/domain/entities/code_file.py +44 -0
- src/domain/entities/dependency_graph.py +110 -0
- src/domain/entities/repository.py +46 -0
- src/domain/exceptions.py +36 -0
- src/domain/repositories/__init__.py +22 -0
- src/domain/repositories/i_code_parser.py +50 -0
- src/domain/repositories/i_dependency_extractor.py +35 -0
- src/domain/repositories/i_embedding_provider.py +31 -0
- src/domain/repositories/i_graph_repository.py +78 -0
- src/domain/repositories/i_vector_repository.py +55 -0
- src/domain/services/__init__.py +8 -0
- src/domain/services/dependency_extractor.py +962 -0
- src/domain/services/semantic_chunk_builder.py +719 -0
- src/domain/value_objects/__init__.py +12 -0
- src/domain/value_objects/chunk_type.py +24 -0
- src/domain/value_objects/dependency_type.py +22 -0
- src/domain/value_objects/node_type.py +22 -0
- src/domain/value_objects/qualified_name.py +78 -0
- src/infrastructure/__init__.py +0 -0
- src/infrastructure/exceptions.py +41 -0
- src/infrastructure/external_apis/__init__.py +0 -0
- src/infrastructure/external_apis/ollama_embedding_provider.py +134 -0
- src/infrastructure/external_apis/openai_embedding_provider.py +231 -0
- src/infrastructure/parsing/__init__.py +20 -0
- src/infrastructure/parsing/config_file_parser.py +347 -0
- src/infrastructure/parsing/gradle_dependency_parser.py +159 -0
- src/infrastructure/parsing/groovy_parser.py +198 -0
- src/infrastructure/parsing/maven_dependency_parser.py +147 -0
- src/infrastructure/parsing/microfrontend_config_parser.py +242 -0
- src/infrastructure/parsing/npm_package_dependency_parser.py +255 -0
- src/infrastructure/parsing/python_parser.py +211 -0
- src/infrastructure/parsing/sql_schema_parser.py +658 -0
- src/infrastructure/parsing/tree_sitter_java_parser.py +434 -0
- src/infrastructure/parsing/tree_sitter_typescript_parser.py +600 -0
- src/infrastructure/persistence/__init__.py +0 -0
- src/infrastructure/persistence/graph_export_repository.py +443 -0
- src/infrastructure/persistence/neo4j_dependency_repository.py +657 -0
- src/infrastructure/persistence/qdrant_vector_repository.py +357 -0
- src/infrastructure/persistence/repository_relationship_repository.py +1312 -0
- src/presentation/__init__.py +0 -0
- src/presentation/api/__init__.py +0 -0
- src/presentation/api/rest/__init__.py +0 -0
- src/presentation/api/rest/exception_handlers.py +174 -0
- src/presentation/api/rest/main.py +80 -0
- src/presentation/api/rest/routers/__init__.py +5 -0
- src/presentation/api/rest/routers/admin.py +103 -0
- src/presentation/api/rest/routers/analysis.py +980 -0
- src/presentation/api/rest/routers/export.py +442 -0
- src/presentation/api/rest/routers/health.py +117 -0
- src/presentation/api/rest/routers/indexing.py +148 -0
- src/presentation/api/rest/routers/relationships.py +1089 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IndexingOrchestrator Implementation (Task 8.2)
|
|
3
|
+
|
|
4
|
+
Manages parallel processing of multiple repositories with concurrency control.
|
|
5
|
+
Uses semaphore to limit concurrent executions.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Callable
|
|
9
|
+
import asyncio
|
|
10
|
+
import structlog
|
|
11
|
+
|
|
12
|
+
from src.application.dtos.batch_processing_result import BatchProcessingResult
|
|
13
|
+
from src.application.dtos.index_result import IndexResult
|
|
14
|
+
from src.application.use_cases.index_repository_use_case import IndexRepositoryUseCase
|
|
15
|
+
from src.application.services.progress_tracker import ProgressUpdate
|
|
16
|
+
|
|
17
|
+
logger = structlog.get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IndexingOrchestrator:
|
|
21
|
+
"""
|
|
22
|
+
Orchestrates parallel processing of multiple repositories.
|
|
23
|
+
|
|
24
|
+
Features:
|
|
25
|
+
- Concurrent processing with configurable limit (semaphore)
|
|
26
|
+
- Individual failure isolation (one failure doesn't stop others)
|
|
27
|
+
- Progress callback for each repository
|
|
28
|
+
- Aggregated results (total, success, failed counts)
|
|
29
|
+
|
|
30
|
+
Requirements: 2.6, 2.7, 2.8
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, index_repository_use_case: IndexRepositoryUseCase):
|
|
34
|
+
"""
|
|
35
|
+
Initialize orchestrator with use case.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
index_repository_use_case: Use case for indexing single repository
|
|
39
|
+
"""
|
|
40
|
+
self.index_repository_use_case = index_repository_use_case
|
|
41
|
+
|
|
42
|
+
async def process_batch(
|
|
43
|
+
self,
|
|
44
|
+
repositories: list[Path],
|
|
45
|
+
parallel: int = 3,
|
|
46
|
+
progress_callback: Callable[[str, ProgressUpdate], None] | None = None
|
|
47
|
+
) -> BatchProcessingResult:
|
|
48
|
+
"""
|
|
49
|
+
Process multiple repositories in parallel with concurrency control.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
repositories: List of repository paths to process
|
|
53
|
+
parallel: Maximum concurrent processes (default: 3)
|
|
54
|
+
progress_callback: Optional callback(repo_name, progress_update)
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
BatchProcessingResult with aggregated counts and results
|
|
58
|
+
"""
|
|
59
|
+
# Handle empty list
|
|
60
|
+
if not repositories:
|
|
61
|
+
logger.info("batch_processing_empty", repository_count=0)
|
|
62
|
+
return BatchProcessingResult(
|
|
63
|
+
total_processed=0,
|
|
64
|
+
success_count=0,
|
|
65
|
+
failed_count=0,
|
|
66
|
+
results=[]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
logger.info(
|
|
70
|
+
"batch_processing_started",
|
|
71
|
+
repository_count=len(repositories),
|
|
72
|
+
parallel_limit=parallel
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Create semaphore for concurrency control
|
|
76
|
+
semaphore = asyncio.Semaphore(parallel)
|
|
77
|
+
|
|
78
|
+
# Process each repository with semaphore
|
|
79
|
+
async def process_with_semaphore(repo_path: Path, index: int) -> IndexResult:
|
|
80
|
+
"""Process single repository with semaphore control."""
|
|
81
|
+
async with semaphore:
|
|
82
|
+
return await self._process_single_repository(
|
|
83
|
+
repo_path,
|
|
84
|
+
index,
|
|
85
|
+
progress_callback
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Create tasks for all repositories
|
|
89
|
+
tasks = [
|
|
90
|
+
process_with_semaphore(repo, i)
|
|
91
|
+
for i, repo in enumerate(repositories)
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
# Execute all tasks concurrently (but limited by semaphore)
|
|
95
|
+
results = await asyncio.gather(*tasks, return_exceptions=False)
|
|
96
|
+
|
|
97
|
+
# Aggregate results
|
|
98
|
+
success_count = sum(1 for r in results if r.status == "success")
|
|
99
|
+
failed_count = sum(1 for r in results if r.status == "error")
|
|
100
|
+
|
|
101
|
+
logger.info(
|
|
102
|
+
"batch_processing_completed",
|
|
103
|
+
total_processed=len(results),
|
|
104
|
+
success_count=success_count,
|
|
105
|
+
failed_count=failed_count
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return BatchProcessingResult(
|
|
109
|
+
total_processed=len(results),
|
|
110
|
+
success_count=success_count,
|
|
111
|
+
failed_count=failed_count,
|
|
112
|
+
results=results
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def _process_single_repository(
|
|
116
|
+
self,
|
|
117
|
+
repository_path: Path,
|
|
118
|
+
index: int,
|
|
119
|
+
progress_callback: Callable[[str, ProgressUpdate], None] | None = None
|
|
120
|
+
) -> IndexResult:
|
|
121
|
+
"""
|
|
122
|
+
Process a single repository with error handling.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
repository_path: Path to repository
|
|
126
|
+
index: Index in batch (for logging)
|
|
127
|
+
progress_callback: Optional progress callback
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
IndexResult (either success or error)
|
|
131
|
+
"""
|
|
132
|
+
repository_name = repository_path.name
|
|
133
|
+
|
|
134
|
+
logger.info(
|
|
135
|
+
"repository_processing_started",
|
|
136
|
+
repository=repository_name,
|
|
137
|
+
index=index,
|
|
138
|
+
path=str(repository_path)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
# Create wrapper callback that adds repository name
|
|
143
|
+
repo_progress_callback = None
|
|
144
|
+
if progress_callback:
|
|
145
|
+
def wrapped_callback(update: ProgressUpdate):
|
|
146
|
+
progress_callback(repository_name, update)
|
|
147
|
+
repo_progress_callback = wrapped_callback
|
|
148
|
+
|
|
149
|
+
# Execute indexing
|
|
150
|
+
result = await self.index_repository_use_case.execute(
|
|
151
|
+
repository_path,
|
|
152
|
+
progress_callback=repo_progress_callback
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
logger.info(
|
|
156
|
+
"repository_processing_completed",
|
|
157
|
+
repository=repository_name,
|
|
158
|
+
status=result.status
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
# Capture exception and return error result
|
|
165
|
+
logger.error(
|
|
166
|
+
"repository_processing_failed",
|
|
167
|
+
repository=repository_name,
|
|
168
|
+
error=str(e),
|
|
169
|
+
error_type=type(e).__name__
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Return error result instead of raising
|
|
173
|
+
return IndexResult(
|
|
174
|
+
status="error",
|
|
175
|
+
repository_name=repository_name,
|
|
176
|
+
files_processed=0,
|
|
177
|
+
chunks_created=0,
|
|
178
|
+
nodes_created=0,
|
|
179
|
+
relationships_created=0,
|
|
180
|
+
error_message=str(e)
|
|
181
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Use case for listing indexed repositories."""
|
|
2
|
+
from typing import List
|
|
3
|
+
import structlog
|
|
4
|
+
|
|
5
|
+
from src.infrastructure.persistence.neo4j_dependency_repository import Neo4jDependencyGraphRepository
|
|
6
|
+
|
|
7
|
+
logger = structlog.get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ListRepositoriesUseCase:
|
|
11
|
+
"""
|
|
12
|
+
Use case for listing all indexed repositories.
|
|
13
|
+
|
|
14
|
+
Queries Neo4j to find all unique repository names from indexed nodes.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, graph_repository: Neo4jDependencyGraphRepository):
|
|
18
|
+
"""
|
|
19
|
+
Initialize use case.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
graph_repository: Neo4j dependency graph repository
|
|
23
|
+
"""
|
|
24
|
+
self.graph_repository = graph_repository
|
|
25
|
+
|
|
26
|
+
async def execute(self) -> List[str]:
|
|
27
|
+
"""
|
|
28
|
+
Execute use case to list repositories.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of unique repository names
|
|
32
|
+
"""
|
|
33
|
+
logger.info("list_repositories_starting")
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
# Query Neo4j for distinct repository names
|
|
37
|
+
repositories = await self._query_repositories()
|
|
38
|
+
|
|
39
|
+
logger.info(
|
|
40
|
+
"list_repositories_completed",
|
|
41
|
+
count=len(repositories)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return repositories
|
|
45
|
+
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error(
|
|
48
|
+
"list_repositories_failed",
|
|
49
|
+
error=str(e),
|
|
50
|
+
error_type=type(e).__name__
|
|
51
|
+
)
|
|
52
|
+
raise
|
|
53
|
+
|
|
54
|
+
async def _query_repositories(self) -> List[str]:
|
|
55
|
+
"""
|
|
56
|
+
Query Neo4j for unique repository names.
|
|
57
|
+
|
|
58
|
+
Filters out 'external' and 'unknown' repositories (libraries).
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of repository names sorted alphabetically
|
|
62
|
+
"""
|
|
63
|
+
query = """
|
|
64
|
+
MATCH (n)
|
|
65
|
+
WHERE n.repository IS NOT NULL
|
|
66
|
+
AND n.repository <> 'external'
|
|
67
|
+
AND n.repository <> 'unknown'
|
|
68
|
+
RETURN DISTINCT n.repository as repository
|
|
69
|
+
ORDER BY repository ASC
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
async with self.graph_repository.driver.session() as session:
|
|
73
|
+
result = await session.run(query)
|
|
74
|
+
records = await result.data()
|
|
75
|
+
|
|
76
|
+
repositories = [record["repository"] for record in records]
|
|
77
|
+
|
|
78
|
+
logger.debug(
|
|
79
|
+
"repositories_queried",
|
|
80
|
+
repositories=repositories,
|
|
81
|
+
count=len(repositories)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return repositories
|