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.
Files changed (86) hide show
  1. graph_dependency_analyzer-0.2.0.dist-info/METADATA +158 -0
  2. graph_dependency_analyzer-0.2.0.dist-info/RECORD +86 -0
  3. graph_dependency_analyzer-0.2.0.dist-info/WHEEL +5 -0
  4. graph_dependency_analyzer-0.2.0.dist-info/entry_points.txt +2 -0
  5. graph_dependency_analyzer-0.2.0.dist-info/top_level.txt +1 -0
  6. src/__init__.py +0 -0
  7. src/application/__init__.py +0 -0
  8. src/application/dtos/__init__.py +0 -0
  9. src/application/dtos/affected_component.py +59 -0
  10. src/application/dtos/batch_processing_result.py +17 -0
  11. src/application/dtos/impact_analysis_response.py +56 -0
  12. src/application/dtos/impact_query_request.py +38 -0
  13. src/application/dtos/index_batch_request.py +53 -0
  14. src/application/dtos/index_batch_response.py +72 -0
  15. src/application/dtos/index_result.py +20 -0
  16. src/application/dtos/repository_index_result.py +79 -0
  17. src/application/exceptions.py +41 -0
  18. src/application/services/__init__.py +0 -0
  19. src/application/services/file_scanner_service.py +258 -0
  20. src/application/services/progress_tracker.py +132 -0
  21. src/application/use_cases/__init__.py +0 -0
  22. src/application/use_cases/clear_all_data_use_case.py +180 -0
  23. src/application/use_cases/index_batch_use_case.py +153 -0
  24. src/application/use_cases/index_repository_use_case.py +683 -0
  25. src/application/use_cases/indexing_orchestrator.py +181 -0
  26. src/application/use_cases/list_repositories_use_case.py +84 -0
  27. src/cli.py +512 -0
  28. src/config/__init__.py +5 -0
  29. src/config/container.py +211 -0
  30. src/config/logging_config.py +123 -0
  31. src/config/settings.py +165 -0
  32. src/domain/__init__.py +0 -0
  33. src/domain/entities/__init__.py +20 -0
  34. src/domain/entities/ast_node.py +32 -0
  35. src/domain/entities/code_chunk.py +45 -0
  36. src/domain/entities/code_file.py +44 -0
  37. src/domain/entities/dependency_graph.py +110 -0
  38. src/domain/entities/repository.py +46 -0
  39. src/domain/exceptions.py +36 -0
  40. src/domain/repositories/__init__.py +22 -0
  41. src/domain/repositories/i_code_parser.py +50 -0
  42. src/domain/repositories/i_dependency_extractor.py +35 -0
  43. src/domain/repositories/i_embedding_provider.py +31 -0
  44. src/domain/repositories/i_graph_repository.py +78 -0
  45. src/domain/repositories/i_vector_repository.py +55 -0
  46. src/domain/services/__init__.py +8 -0
  47. src/domain/services/dependency_extractor.py +962 -0
  48. src/domain/services/semantic_chunk_builder.py +719 -0
  49. src/domain/value_objects/__init__.py +12 -0
  50. src/domain/value_objects/chunk_type.py +24 -0
  51. src/domain/value_objects/dependency_type.py +22 -0
  52. src/domain/value_objects/node_type.py +22 -0
  53. src/domain/value_objects/qualified_name.py +78 -0
  54. src/infrastructure/__init__.py +0 -0
  55. src/infrastructure/exceptions.py +41 -0
  56. src/infrastructure/external_apis/__init__.py +0 -0
  57. src/infrastructure/external_apis/ollama_embedding_provider.py +134 -0
  58. src/infrastructure/external_apis/openai_embedding_provider.py +231 -0
  59. src/infrastructure/parsing/__init__.py +20 -0
  60. src/infrastructure/parsing/config_file_parser.py +347 -0
  61. src/infrastructure/parsing/gradle_dependency_parser.py +159 -0
  62. src/infrastructure/parsing/groovy_parser.py +198 -0
  63. src/infrastructure/parsing/maven_dependency_parser.py +147 -0
  64. src/infrastructure/parsing/microfrontend_config_parser.py +242 -0
  65. src/infrastructure/parsing/npm_package_dependency_parser.py +255 -0
  66. src/infrastructure/parsing/python_parser.py +211 -0
  67. src/infrastructure/parsing/sql_schema_parser.py +658 -0
  68. src/infrastructure/parsing/tree_sitter_java_parser.py +434 -0
  69. src/infrastructure/parsing/tree_sitter_typescript_parser.py +600 -0
  70. src/infrastructure/persistence/__init__.py +0 -0
  71. src/infrastructure/persistence/graph_export_repository.py +443 -0
  72. src/infrastructure/persistence/neo4j_dependency_repository.py +657 -0
  73. src/infrastructure/persistence/qdrant_vector_repository.py +357 -0
  74. src/infrastructure/persistence/repository_relationship_repository.py +1312 -0
  75. src/presentation/__init__.py +0 -0
  76. src/presentation/api/__init__.py +0 -0
  77. src/presentation/api/rest/__init__.py +0 -0
  78. src/presentation/api/rest/exception_handlers.py +174 -0
  79. src/presentation/api/rest/main.py +80 -0
  80. src/presentation/api/rest/routers/__init__.py +5 -0
  81. src/presentation/api/rest/routers/admin.py +103 -0
  82. src/presentation/api/rest/routers/analysis.py +980 -0
  83. src/presentation/api/rest/routers/export.py +442 -0
  84. src/presentation/api/rest/routers/health.py +117 -0
  85. src/presentation/api/rest/routers/indexing.py +148 -0
  86. 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