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,347 @@
1
+ """Configuration file parser for extracting APIs, databases, and services.
2
+
3
+ This module provides parsing capabilities for application.properties,
4
+ application.yml, and .env files to extract external dependencies.
5
+ """
6
+
7
+ import re
8
+ from dataclasses import dataclass, field
9
+ from typing import Any
10
+ import yaml
11
+ from src.domain.value_objects.dependency_type import DependencyType
12
+
13
+
14
+ @dataclass
15
+ class ParsedConfig:
16
+ """Result of parsing configuration files.
17
+
18
+ Attributes:
19
+ databases: List of database connections (JDBC URLs)
20
+ apis: List of API endpoints (HTTP/HTTPS URLs)
21
+ services: List of microservice names
22
+ feature_flags: List of feature flag definitions (Task 11.3)
23
+ """
24
+
25
+ databases: list[dict[str, Any]] = field(default_factory=list)
26
+ apis: list[dict[str, Any]] = field(default_factory=list)
27
+ services: list[dict[str, Any]] = field(default_factory=list)
28
+ feature_flags: list[dict[str, Any]] = field(default_factory=list) # Task 11.3
29
+
30
+
31
+ class ConfigFileParser:
32
+ """Parser for application configuration files.
33
+
34
+ Parses:
35
+ - application.properties (Java properties format)
36
+ - application.yml (YAML format)
37
+ - .env files (KEY=VALUE format, including frontend env vars)
38
+ - TypeScript/JavaScript config files (config.ts, environment.ts) - Task 11.2
39
+
40
+ Extracts:
41
+ - API URLs (http://, https://, wss://)
42
+ - Database connections (jdbc:)
43
+ - Microservice names (service identifiers)
44
+ - Feature flags (Task 11.3)
45
+ - Environment-specific configurations (Task 11.1)
46
+ """
47
+
48
+ # Regex patterns
49
+ JDBC_PATTERN = re.compile(r'jdbc:[^:]+://[^\s]+')
50
+ HTTP_URL_PATTERN = re.compile(r'https?://[^\s\'"]+')
51
+ WS_URL_PATTERN = re.compile(r'wss?://[^\s\'"]+') # WebSocket URLs
52
+
53
+ # Keys that typically contain API URLs
54
+ API_KEYS = {
55
+ "url", "endpoint", "host", "api", "gateway", "service.url",
56
+ "external", "client", "rest", "graphql"
57
+ }
58
+
59
+ # Keys that typically contain service names
60
+ SERVICE_KEYS = {"service", "name", "microservice", "app"}
61
+
62
+ # Frontend environment variable prefixes (Task 11.1)
63
+ FRONTEND_ENV_PREFIXES = {
64
+ "REACT_APP_", "VITE_", "NEXT_PUBLIC_", "NG_APP_",
65
+ "VUE_APP_", "NUXT_PUBLIC_"
66
+ }
67
+
68
+ # Feature flag patterns (Task 11.3)
69
+ FEATURE_FLAG_PATTERNS = {
70
+ "FEATURE_", "FEAT_", "FLAG_", "ENABLE_", "DISABLE_"
71
+ }
72
+
73
+ def parse_properties(self, content: str) -> ParsedConfig:
74
+ """Parse Java properties file (application.properties).
75
+
76
+ Args:
77
+ content: String content of properties file
78
+
79
+ Returns:
80
+ ParsedConfig with extracted dependencies
81
+ """
82
+ result = ParsedConfig()
83
+
84
+ # Remove comments
85
+ lines = [line for line in content.split('\n') if line.strip() and not line.strip().startswith('#')]
86
+
87
+ for line in lines:
88
+ if '=' not in line:
89
+ continue
90
+
91
+ key, value = line.split('=', 1)
92
+ key = key.strip()
93
+ value = value.strip()
94
+
95
+ # Extract databases (JDBC)
96
+ if value.startswith('jdbc:'):
97
+ result.databases.append({
98
+ "url": value,
99
+ "type": DependencyType.DATABASE,
100
+ "source": key,
101
+ })
102
+
103
+ # Extract APIs (HTTP/HTTPS)
104
+ elif value.startswith(('http://', 'https://')):
105
+ result.apis.append({
106
+ "url": value,
107
+ "type": DependencyType.API,
108
+ "source": key,
109
+ })
110
+
111
+ # Extract service names
112
+ elif any(svc_key in key.lower() for svc_key in self.SERVICE_KEYS):
113
+ if not value.startswith(('http://', 'https://', 'jdbc:')):
114
+ result.services.append({
115
+ "name": value,
116
+ "type": DependencyType.MICROSERVICE,
117
+ "source": key,
118
+ })
119
+
120
+ return result
121
+
122
+ def parse_yaml(self, content: str) -> ParsedConfig:
123
+ """Parse YAML configuration file (application.yml).
124
+
125
+ Args:
126
+ content: String content of YAML file
127
+
128
+ Returns:
129
+ ParsedConfig with extracted dependencies
130
+ """
131
+ result = ParsedConfig()
132
+
133
+ try:
134
+ data = yaml.safe_load(content)
135
+ if data:
136
+ self._extract_from_dict(data, result)
137
+ except yaml.YAMLError:
138
+ # If YAML parsing fails, return empty result
139
+ pass
140
+
141
+ return result
142
+
143
+ def parse_env(self, content: str, environment: str | None = None, detect_feature_flags: bool = False) -> ParsedConfig:
144
+ """Parse .env file (KEY=VALUE format).
145
+
146
+ Args:
147
+ content: String content of .env file
148
+ environment: Environment tag (e.g., "development", "production", "staging") - Task 11.1
149
+ detect_feature_flags: Whether to detect and extract feature flags - Task 11.3
150
+
151
+ Returns:
152
+ ParsedConfig with extracted dependencies
153
+ """
154
+ result = ParsedConfig()
155
+
156
+ # Remove comments
157
+ lines = [line for line in content.split('\n') if line.strip() and not line.strip().startswith('#')]
158
+
159
+ for line in lines:
160
+ if '=' not in line:
161
+ continue
162
+
163
+ key, value = line.split('=', 1)
164
+ key = key.strip()
165
+ value = value.strip()
166
+
167
+ # Task 11.3: Detect feature flags
168
+ if detect_feature_flags and self._is_feature_flag(key):
169
+ result.feature_flags.append({
170
+ "name": key,
171
+ "enabled": self._parse_boolean(value),
172
+ "source": "env",
173
+ "environment": environment
174
+ })
175
+
176
+ # Extract databases (JDBC)
177
+ if value.startswith('jdbc:'):
178
+ result.databases.append({
179
+ "url": value,
180
+ "type": DependencyType.DATABASE,
181
+ "source": key,
182
+ "environment": environment # Task 11.1
183
+ })
184
+
185
+ # Extract WebSocket URLs (Task 11.1)
186
+ elif value.startswith(('ws://', 'wss://')):
187
+ result.apis.append({
188
+ "url": value,
189
+ "type": DependencyType.API,
190
+ "source": key,
191
+ "environment": environment # Task 11.1
192
+ })
193
+
194
+ # Extract APIs (HTTP/HTTPS)
195
+ elif value.startswith(('http://', 'https://')):
196
+ result.apis.append({
197
+ "url": value,
198
+ "type": DependencyType.API,
199
+ "source": key,
200
+ "environment": environment # Task 11.1
201
+ })
202
+
203
+ # Extract service names
204
+ elif any(svc_key in key.lower() for svc_key in self.SERVICE_KEYS):
205
+ if not value.startswith(('http://', 'https://', 'jdbc:', 'ws://', 'wss://')):
206
+ result.services.append({
207
+ "name": value,
208
+ "type": DependencyType.MICROSERVICE,
209
+ "source": key,
210
+ "environment": environment # Task 11.1
211
+ })
212
+
213
+ return result
214
+
215
+ def _is_feature_flag(self, key: str) -> bool:
216
+ """Check if key matches feature flag pattern (Task 11.3)."""
217
+ key_upper = key.upper()
218
+ return any(pattern in key_upper for pattern in self.FEATURE_FLAG_PATTERNS)
219
+
220
+ def _parse_boolean(self, value: str) -> bool:
221
+ """Parse boolean value from string (Task 11.3)."""
222
+ value_lower = value.lower()
223
+ return value_lower in ("true", "1", "yes", "enabled", "on")
224
+
225
+ def parse_typescript_config(
226
+ self,
227
+ content: str,
228
+ typescript_parser=None,
229
+ environment: str | None = None,
230
+ detect_feature_flags: bool = False
231
+ ) -> ParsedConfig:
232
+ """Parse TypeScript/JavaScript config files (Task 11.2).
233
+
234
+ Args:
235
+ content: String content of config.ts or environment.ts file
236
+ typescript_parser: TreeSitterTypeScriptParser instance for AST parsing
237
+ environment: Environment tag (e.g., "development", "production")
238
+ detect_feature_flags: Whether to detect and extract feature flags
239
+
240
+ Returns:
241
+ ParsedConfig with extracted dependencies
242
+ """
243
+ result = ParsedConfig()
244
+
245
+ if not typescript_parser:
246
+ return result
247
+
248
+ # Parse with TypeScript parser to extract string literals
249
+ try:
250
+ # For testing, typescript_parser might be a mock
251
+ if hasattr(typescript_parser, 'parse_file'):
252
+ # Mock or real parser
253
+ parse_result = typescript_parser.parse_file(content)
254
+ else:
255
+ return result
256
+
257
+ # Extract URLs from string literals
258
+ if hasattr(parse_result, 'string_literals'):
259
+ for literal in parse_result.string_literals:
260
+ if isinstance(literal, str):
261
+ # Check for URLs
262
+ if literal.startswith(('http://', 'https://', 'ws://', 'wss://')):
263
+ result.apis.append({
264
+ "url": literal,
265
+ "type": DependencyType.API,
266
+ "source": "typescript_config",
267
+ "environment": environment
268
+ })
269
+
270
+ # Detect environment variable references (Task 11.2)
271
+ if hasattr(parse_result, 'method_calls'):
272
+ for call in parse_result.method_calls:
273
+ call_name = call.get("name", "")
274
+ if "process.env" in call_name:
275
+ # Extract full env variable name (keep REACT_APP_, VITE_, etc.)
276
+ if "." in call_name:
277
+ env_var = call_name.split(".")[-1] # Get last part after final dot
278
+ else:
279
+ env_var = call_name
280
+ result.apis.append({
281
+ "url": None,
282
+ "type": DependencyType.API,
283
+ "source": "typescript_config",
284
+ "environment": environment,
285
+ "environment_dependent": True,
286
+ "env_variable": env_var
287
+ })
288
+
289
+ # Task 11.3: Detect feature flags in TypeScript config
290
+ if detect_feature_flags:
291
+ # Look for featureFlags, features, flags patterns in content
292
+ if any(pattern in content for pattern in ["featureFlags", "feature_flags", "FEATURE_FLAGS"]):
293
+ # Simple detection: if we find feature flag patterns in content
294
+ # Add a generic feature flag entry
295
+ result.feature_flags.append({
296
+ "name": "detected_in_typescript_config",
297
+ "enabled": True,
298
+ "source": "typescript_config",
299
+ "environment": environment
300
+ })
301
+
302
+ except Exception:
303
+ # Graceful degradation - return partial results
304
+ pass
305
+
306
+ return result
307
+
308
+ def _extract_from_dict(self, data: dict[str, Any], result: ParsedConfig, parent_key: str = "") -> None:
309
+ """Recursively extract dependencies from nested dictionary.
310
+
311
+ Args:
312
+ data: Dictionary to extract from
313
+ result: ParsedConfig to populate
314
+ parent_key: Parent key path for context
315
+ """
316
+ for key, value in data.items():
317
+ full_key = f"{parent_key}.{key}" if parent_key else key
318
+
319
+ if isinstance(value, dict):
320
+ # Recurse into nested dictionaries
321
+ self._extract_from_dict(value, result, full_key)
322
+
323
+ elif isinstance(value, str):
324
+ # Extract JDBC URLs
325
+ if value.startswith('jdbc:'):
326
+ result.databases.append({
327
+ "url": value,
328
+ "type": DependencyType.DATABASE,
329
+ "source": full_key,
330
+ })
331
+
332
+ # Extract HTTP/HTTPS URLs
333
+ elif value.startswith(('http://', 'https://')):
334
+ result.apis.append({
335
+ "url": value,
336
+ "type": DependencyType.API,
337
+ "source": full_key,
338
+ })
339
+
340
+ # Extract service names
341
+ elif any(svc_key in full_key.lower() for svc_key in self.SERVICE_KEYS):
342
+ if not value.startswith(('http://', 'https://', 'jdbc:')):
343
+ result.services.append({
344
+ "name": value,
345
+ "type": DependencyType.MICROSERVICE,
346
+ "source": full_key,
347
+ })
@@ -0,0 +1,159 @@
1
+ """Gradle build.gradle and build.gradle.kts dependency parser.
2
+
3
+ This module provides parsing capabilities for Gradle build files to extract
4
+ dependency information for the dependency analysis system.
5
+ """
6
+
7
+ import re
8
+ from typing import TypedDict
9
+ from src.domain.value_objects.dependency_type import DependencyType
10
+
11
+
12
+ class GradleDependency(TypedDict):
13
+ """Type definition for Gradle dependency dictionary."""
14
+
15
+ group_id: str
16
+ artifact_id: str
17
+ version: str
18
+ scope: str
19
+ type: DependencyType
20
+
21
+
22
+ class GradleDependencyParser:
23
+ """Parser for Gradle build.gradle and build.gradle.kts files.
24
+
25
+ Parses both Groovy (build.gradle) and Kotlin (build.gradle.kts) syntax.
26
+ Extracts dependency information: group:artifact:version format.
27
+ Filters out test-scoped dependencies by default.
28
+
29
+ Attributes:
30
+ SCOPE_PATTERNS: Regex patterns for different dependency scopes
31
+ DEPENDENCY_PATTERN: Regex pattern to extract dependency coordinates
32
+ """
33
+
34
+ # Gradle dependency scopes to include (exclude test scopes)
35
+ INCLUDE_SCOPES = {"implementation", "api", "compileOnly", "runtimeOnly"}
36
+ TEST_SCOPES = {"testImplementation", "testCompileOnly", "testRuntimeOnly"}
37
+
38
+ # Regex patterns for dependency extraction
39
+ # Matches: implementation 'group:artifact:version'
40
+ # Matches: implementation("group:artifact:version")
41
+ # Matches multiline: implementation(\n "group:artifact:version"\n)
42
+ DEPENDENCY_PATTERN = re.compile(
43
+ r'''
44
+ (?P<scope>implementation|api|compileOnly|runtimeOnly|testImplementation|testCompileOnly|testRuntimeOnly)
45
+ \s*
46
+ \(?\s* # Optional opening parenthesis
47
+ ["'] # Opening quote (single or double)
48
+ \s*
49
+ (?P<coords>[^"']+) # Dependency coordinates
50
+ \s*
51
+ ["'] # Closing quote (single or double)
52
+ \s*\)? # Optional closing parenthesis
53
+ ''',
54
+ re.VERBOSE | re.MULTILINE | re.DOTALL,
55
+ )
56
+
57
+ # Pattern to extract group:artifact:version
58
+ COORDS_PATTERN = re.compile(r'^([^:]+):([^:]+)(?::([^:]+))?$')
59
+
60
+ MANAGED_VERSION = "managed"
61
+
62
+ def parse(self, gradle_content: str) -> list[GradleDependency]:
63
+ """Parse build.gradle or build.gradle.kts content and extract dependencies.
64
+
65
+ Args:
66
+ gradle_content: String content of build.gradle or build.gradle.kts file
67
+
68
+ Returns:
69
+ List of GradleDependency dictionaries with keys:
70
+ - group_id: Gradle group (e.g., 'org.springframework.boot')
71
+ - artifact_id: Gradle artifact (e.g., 'spring-boot-starter-web')
72
+ - version: Version string or 'managed' if not specified
73
+ - scope: Dependency scope (implementation, api, compileOnly, runtimeOnly)
74
+ - type: Always DependencyType.LIBRARY
75
+
76
+ Note:
77
+ Test-scoped dependencies (testImplementation, etc) are filtered out.
78
+ Comments (// and /* */) are removed before parsing.
79
+ """
80
+ # Remove comments
81
+ clean_content = self._remove_comments(gradle_content)
82
+
83
+ # Find all dependency declarations
84
+ matches = self.DEPENDENCY_PATTERN.finditer(clean_content)
85
+
86
+ dependencies: list[GradleDependency] = []
87
+
88
+ for match in matches:
89
+ scope = match.group("scope")
90
+ coords = match.group("coords").strip()
91
+
92
+ # Filter out test dependencies
93
+ if scope in self.TEST_SCOPES:
94
+ continue
95
+
96
+ # Parse coordinates
97
+ dependency = self._parse_coordinates(coords, scope)
98
+
99
+ if dependency:
100
+ dependencies.append(dependency)
101
+
102
+ return dependencies
103
+
104
+ def _remove_comments(self, content: str) -> str:
105
+ """Remove single-line and multi-line comments from Gradle file.
106
+
107
+ Args:
108
+ content: Original Gradle file content
109
+
110
+ Returns:
111
+ Content with comments removed
112
+ """
113
+ # Remove single-line comments
114
+ content = re.sub(r'//.*?$', '', content, flags=re.MULTILINE)
115
+
116
+ # Remove multi-line comments
117
+ content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
118
+
119
+ return content
120
+
121
+ def _parse_coordinates(self, coords: str, scope: str) -> GradleDependency | None:
122
+ """Parse dependency coordinates in format 'group:artifact:version'.
123
+
124
+ Args:
125
+ coords: Dependency coordinates string
126
+ scope: Dependency scope (implementation, api, etc)
127
+
128
+ Returns:
129
+ GradleDependency dictionary or None if parsing fails
130
+ """
131
+ # Store original coords to check for variables
132
+ has_variable = "${" in coords
133
+
134
+ # Handle variable interpolation (${variable})
135
+ # For simplicity, treat as managed version
136
+ if has_variable:
137
+ coords = re.sub(r'\$\{[^}]+\}', 'managed', coords)
138
+
139
+ # Match group:artifact:version pattern
140
+ match = self.COORDS_PATTERN.match(coords.strip())
141
+
142
+ if not match:
143
+ return None
144
+
145
+ group_id = match.group(1).strip()
146
+ artifact_id = match.group(2).strip()
147
+ version = match.group(3).strip() if match.group(3) else self.MANAGED_VERSION
148
+
149
+ # Skip if essential fields are missing
150
+ if not group_id or not artifact_id:
151
+ return None
152
+
153
+ return GradleDependency(
154
+ group_id=group_id,
155
+ artifact_id=artifact_id,
156
+ version=version,
157
+ scope=scope,
158
+ type=DependencyType.LIBRARY,
159
+ )