apisec-code-bolt 0.1.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 (111) hide show
  1. apisec_code_bolt/__init__.py +42 -0
  2. apisec_code_bolt/__main__.py +11 -0
  3. apisec_code_bolt/analysis/__init__.py +96 -0
  4. apisec_code_bolt/analysis/analyzer.py +2309 -0
  5. apisec_code_bolt/analysis/binding_tracker.py +341 -0
  6. apisec_code_bolt/analysis/call_graph.py +1197 -0
  7. apisec_code_bolt/analysis/call_graph_types.py +332 -0
  8. apisec_code_bolt/analysis/call_resolver.py +988 -0
  9. apisec_code_bolt/analysis/capability_tagger.py +322 -0
  10. apisec_code_bolt/analysis/config_scanner.py +197 -0
  11. apisec_code_bolt/analysis/data_flow.py +1883 -0
  12. apisec_code_bolt/analysis/dependency_extractor.py +959 -0
  13. apisec_code_bolt/analysis/flow_analysis.py +1406 -0
  14. apisec_code_bolt/analysis/hof_catalog.py +61 -0
  15. apisec_code_bolt/analysis/integration_detector.py +1399 -0
  16. apisec_code_bolt/analysis/literal_scanner.py +300 -0
  17. apisec_code_bolt/analysis/path_normalizer.py +55 -0
  18. apisec_code_bolt/analysis/read_site_detector.py +310 -0
  19. apisec_code_bolt/analysis/request_patterns.py +162 -0
  20. apisec_code_bolt/analysis/sensitivity_classifier.py +224 -0
  21. apisec_code_bolt/analysis/sink_evidence.py +333 -0
  22. apisec_code_bolt/analysis/url_prefix_resolver.py +338 -0
  23. apisec_code_bolt/cli/__init__.py +5 -0
  24. apisec_code_bolt/cli/exit_codes.py +17 -0
  25. apisec_code_bolt/cli/main.py +1069 -0
  26. apisec_code_bolt/cloud/__init__.py +1 -0
  27. apisec_code_bolt/cloud/apisec_client.py +118 -0
  28. apisec_code_bolt/cloud/client.py +255 -0
  29. apisec_code_bolt/core/__init__.py +75 -0
  30. apisec_code_bolt/core/config.py +528 -0
  31. apisec_code_bolt/core/credentials.py +65 -0
  32. apisec_code_bolt/core/discovery.py +433 -0
  33. apisec_code_bolt/core/log_format.py +115 -0
  34. apisec_code_bolt/core/manifest.py +1009 -0
  35. apisec_code_bolt/core/repo.py +280 -0
  36. apisec_code_bolt/core/state.py +59 -0
  37. apisec_code_bolt/core/telemetry.py +451 -0
  38. apisec_code_bolt/core/types.py +587 -0
  39. apisec_code_bolt/fingerprinting/__init__.py +1 -0
  40. apisec_code_bolt/frameworks/__init__.py +29 -0
  41. apisec_code_bolt/frameworks/_jwt_common.py +50 -0
  42. apisec_code_bolt/frameworks/auth_helpers.py +437 -0
  43. apisec_code_bolt/frameworks/base.py +608 -0
  44. apisec_code_bolt/frameworks/dotnet/__init__.py +17 -0
  45. apisec_code_bolt/frameworks/dotnet/_path_helpers.py +43 -0
  46. apisec_code_bolt/frameworks/dotnet/aspnet_plugin.py +2546 -0
  47. apisec_code_bolt/frameworks/dotnet/grpc_plugin.py +559 -0
  48. apisec_code_bolt/frameworks/dotnet/jwt_config_extractor.py +545 -0
  49. apisec_code_bolt/frameworks/dotnet/legacy_aspnet_plugin.py +732 -0
  50. apisec_code_bolt/frameworks/dotnet/refit_plugin.py +374 -0
  51. apisec_code_bolt/frameworks/dotnet/wcf_plugin.py +1239 -0
  52. apisec_code_bolt/frameworks/java/__init__.py +6 -0
  53. apisec_code_bolt/frameworks/java/_annotations.py +167 -0
  54. apisec_code_bolt/frameworks/java/_constraints.py +128 -0
  55. apisec_code_bolt/frameworks/java/graphql_plugin.py +287 -0
  56. apisec_code_bolt/frameworks/java/jaxrs_plugin.py +748 -0
  57. apisec_code_bolt/frameworks/java/jwt_config_extractor.py +361 -0
  58. apisec_code_bolt/frameworks/java/micronaut_plugin.py +1059 -0
  59. apisec_code_bolt/frameworks/java/spring_plugin.py +1293 -0
  60. apisec_code_bolt/frameworks/js/__init__.py +8 -0
  61. apisec_code_bolt/frameworks/js/express_plugin.py +391 -0
  62. apisec_code_bolt/frameworks/js/fastify_plugin.py +381 -0
  63. apisec_code_bolt/frameworks/js/graphql_plugin.py +198 -0
  64. apisec_code_bolt/frameworks/js/nestjs_plugin.py +423 -0
  65. apisec_code_bolt/frameworks/python/__init__.py +19 -0
  66. apisec_code_bolt/frameworks/python/celery_plugin.py +393 -0
  67. apisec_code_bolt/frameworks/python/click_plugin.py +427 -0
  68. apisec_code_bolt/frameworks/python/django_plugin.py +867 -0
  69. apisec_code_bolt/frameworks/python/fastapi/__init__.py +28 -0
  70. apisec_code_bolt/frameworks/python/fastapi/plugin.py +1390 -0
  71. apisec_code_bolt/frameworks/python/flask_plugin.py +205 -0
  72. apisec_code_bolt/frameworks/python/graphql_plugin.py +274 -0
  73. apisec_code_bolt/frameworks/python/prefect_plugin.py +251 -0
  74. apisec_code_bolt/frameworks/python/webhook_plugin.py +255 -0
  75. apisec_code_bolt/parsing/__init__.py +62 -0
  76. apisec_code_bolt/parsing/base.py +554 -0
  77. apisec_code_bolt/parsing/csharp/__init__.py +5 -0
  78. apisec_code_bolt/parsing/csharp/language_services.py +203 -0
  79. apisec_code_bolt/parsing/csharp/literals.py +72 -0
  80. apisec_code_bolt/parsing/csharp/parser.py +1158 -0
  81. apisec_code_bolt/parsing/csharp/type_resolver.py +568 -0
  82. apisec_code_bolt/parsing/js/__init__.py +5 -0
  83. apisec_code_bolt/parsing/js/language_services.py +118 -0
  84. apisec_code_bolt/parsing/js/parser.py +622 -0
  85. apisec_code_bolt/parsing/jvm/__init__.py +7 -0
  86. apisec_code_bolt/parsing/jvm/language_services.py +270 -0
  87. apisec_code_bolt/parsing/jvm/parser.py +774 -0
  88. apisec_code_bolt/parsing/jvm/type_resolver.py +422 -0
  89. apisec_code_bolt/parsing/python/__init__.py +150 -0
  90. apisec_code_bolt/parsing/python/cbv_extractor.py +606 -0
  91. apisec_code_bolt/parsing/python/constant_resolver.py +500 -0
  92. apisec_code_bolt/parsing/python/cross_file_resolver.py +1054 -0
  93. apisec_code_bolt/parsing/python/dynamic_route_detector.py +532 -0
  94. apisec_code_bolt/parsing/python/expression_utils.py +221 -0
  95. apisec_code_bolt/parsing/python/extraction_types.py +271 -0
  96. apisec_code_bolt/parsing/python/language_services.py +487 -0
  97. apisec_code_bolt/parsing/python/parameter_analyzer.py +789 -0
  98. apisec_code_bolt/parsing/python/parser.py +719 -0
  99. apisec_code_bolt/parsing/python/path_resolver.py +576 -0
  100. apisec_code_bolt/parsing/python/router_registry.py +806 -0
  101. apisec_code_bolt/parsing/python/type_resolver.py +730 -0
  102. apisec_code_bolt/parsing/python/visitors.py +1544 -0
  103. apisec_code_bolt/parsing/services.py +544 -0
  104. apisec_code_bolt/query/__init__.py +1 -0
  105. apisec_code_bolt/query/ast_cache.py +182 -0
  106. apisec_code_bolt/query/executor.py +283 -0
  107. apisec_code_bolt/query/handlers.py +832 -0
  108. apisec_code_bolt-0.1.0.dist-info/METADATA +230 -0
  109. apisec_code_bolt-0.1.0.dist-info/RECORD +111 -0
  110. apisec_code_bolt-0.1.0.dist-info/WHEEL +4 -0
  111. apisec_code_bolt-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,422 @@
1
+ """
2
+ Java type resolver for cross-file type resolution.
3
+
4
+ Implements the TypeResolver protocol for Java projects.
5
+ Builds a symbol table from parsed Java files and resolves:
6
+ - Simple class names via import declarations
7
+ - Qualified names directly
8
+ - Same-package implicit visibility (no import required)
9
+ - Inheritance chains for model detection
10
+ - Generic type parameters (List<User> → User)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ import re
17
+ from pathlib import Path
18
+
19
+ from ..base import ParsedClass, ParsedFile
20
+ from ..services import ResolvedField, ResolvedType
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Strip Java generic type parameters: "List<User>" → "List", "Map<K,V>" → "Map"
25
+ _GENERIC_RE = re.compile(r"<[^>]*>")
26
+
27
+ # Annotations that indicate a class is a "model" (DTO, entity, value object)
28
+ _MODEL_ANNOTATIONS = frozenset(
29
+ {
30
+ # JPA / Hibernate
31
+ "Entity",
32
+ "Embeddable",
33
+ "MappedSuperclass",
34
+ # Lombok
35
+ "Data",
36
+ "Value",
37
+ # Jackson
38
+ "JsonProperty",
39
+ # Spring
40
+ "RequestBody",
41
+ "ResponseBody",
42
+ # Generic / custom
43
+ "DTO",
44
+ "Record",
45
+ }
46
+ )
47
+
48
+ # Annotations that indicate a Spring-managed bean
49
+ _BEAN_ANNOTATIONS = frozenset(
50
+ {
51
+ "Component",
52
+ "Service",
53
+ "Repository",
54
+ "Controller",
55
+ "RestController",
56
+ "Configuration",
57
+ "Bean",
58
+ }
59
+ )
60
+
61
+ # Built-in Java types that are never in the symbol table
62
+ _JAVA_BUILTINS = frozenset(
63
+ {
64
+ "String",
65
+ "Integer",
66
+ "Long",
67
+ "Double",
68
+ "Float",
69
+ "Boolean",
70
+ "Byte",
71
+ "Short",
72
+ "Character",
73
+ "Object",
74
+ "Number",
75
+ "Comparable",
76
+ "Serializable",
77
+ "Iterable",
78
+ "Collection",
79
+ "List",
80
+ "Set",
81
+ "Map",
82
+ "Queue",
83
+ "Deque",
84
+ "ArrayList",
85
+ "LinkedList",
86
+ "HashMap",
87
+ "HashSet",
88
+ "TreeMap",
89
+ "TreeSet",
90
+ "Optional",
91
+ "void",
92
+ "int",
93
+ "long",
94
+ "double",
95
+ "float",
96
+ "boolean",
97
+ "byte",
98
+ "short",
99
+ "char",
100
+ "Enum",
101
+ "Exception",
102
+ "RuntimeException",
103
+ "Throwable",
104
+ "Error",
105
+ }
106
+ )
107
+
108
+
109
+ def _strip_generics(name: str) -> str:
110
+ """Strip generic type parameters from a Java type name.
111
+
112
+ Examples:
113
+ "List<User>" → "User" (unwrap single-param container)
114
+ "Map<String, User>" → "Map" (ambiguous — return outer type)
115
+ "ResponseEntity<Dto>" → "Dto" (unwrap single-param wrapper)
116
+ "CreateUserRequest" → "CreateUserRequest"
117
+ """
118
+ name = name.strip()
119
+ if "<" not in name:
120
+ return name
121
+
122
+ # Extract the outer type and the raw inner type (preserve inner generics for recursion)
123
+ outer = name[: name.index("<")].strip()
124
+ inner_raw = name[name.index("<") + 1 : name.rindex(">")].strip()
125
+
126
+ # Common single-param containers/wrappers — prefer the inner type
127
+ _WRAPPER_TYPES = frozenset(
128
+ {
129
+ "Optional",
130
+ "List",
131
+ "Set",
132
+ "Collection",
133
+ "Iterable",
134
+ "ResponseEntity",
135
+ "HttpEntity",
136
+ "CompletableFuture",
137
+ "Future",
138
+ "Mono",
139
+ "Flux", # Project Reactor
140
+ }
141
+ )
142
+ if outer in _WRAPPER_TYPES and "," not in inner_raw and inner_raw:
143
+ # Recurse so ResponseEntity<List<User>> → List<User> → User
144
+ return _strip_generics(inner_raw)
145
+
146
+ # Multi-param or unknown — return outer (Map<K,V> → Map)
147
+ return outer
148
+
149
+
150
+ class JavaTypeResolver:
151
+ """
152
+ Cross-file type resolver for Java projects.
153
+
154
+ Implements the TypeResolver protocol.
155
+
156
+ Resolution order:
157
+ 1. Direct qualified-name lookup in symbol table
158
+ 2. Generic stripping + retry (List<User> → User)
159
+ 3. File-local explicit import map
160
+ 4. Wildcard package scan (import com.example.*)
161
+ 5. Same-package implicit visibility (no import needed)
162
+ 6. Project-wide simple-name index (unambiguous)
163
+ """
164
+
165
+ def __init__(self, parsed_files: list[ParsedFile]) -> None:
166
+ # symbol_table: qualified_name → ParsedClass
167
+ self._symbol_table: dict[str, ParsedClass] = {}
168
+ # simple_name → list of qualified names (for ambiguous resolution)
169
+ self._simple_name_index: dict[str, list[str]] = {}
170
+ # per-file import map: file_path → { simple_name → qualified_name }
171
+ self._file_imports: dict[Path, dict[str, str]] = {}
172
+ # per-file wildcard packages: file_path → [ "com.example.dto" ]
173
+ self._file_wildcards: dict[Path, list[str]] = {}
174
+ # per-file package: file_path → "com.example.controller"
175
+ self._file_package: dict[Path, str] = {}
176
+ # package → list of qualified names of classes in that package
177
+ self._package_index: dict[str, list[str]] = {}
178
+ # ancestor cache: avoids repeated O(n) scans per class
179
+ self._ancestor_cache: dict[str, list[str]] = {}
180
+
181
+ self._build(parsed_files)
182
+
183
+ # -------------------------------------------------------------------------
184
+ # Build
185
+ # -------------------------------------------------------------------------
186
+
187
+ def _build(self, parsed_files: list[ParsedFile]) -> None:
188
+ # Phase 1: build symbol table + package index
189
+ for pf in parsed_files:
190
+ if not pf.success:
191
+ continue
192
+ for cls in pf.classes:
193
+ qname = cls.qualified_name.full
194
+ self._symbol_table[qname] = cls
195
+ simple = cls.name
196
+ self._simple_name_index.setdefault(simple, []).append(qname)
197
+
198
+ # Track package membership for same-package resolution
199
+ if "." in qname:
200
+ pkg = qname.rsplit(".", 1)[0]
201
+ self._package_index.setdefault(pkg, []).append(qname)
202
+
203
+ # Phase 2: build per-file import maps + package names
204
+ for pf in parsed_files:
205
+ if not pf.success:
206
+ continue
207
+ import_map: dict[str, str] = {}
208
+ wildcards: list[str] = []
209
+ pkg = ""
210
+ for imp in pf.imports:
211
+ if imp.names:
212
+ # "import com.example.User" → { "User": "com.example.User" }
213
+ for name in imp.names:
214
+ qn = f"{imp.module}.{name}" if imp.module else name
215
+ import_map[name] = qn
216
+ else:
217
+ # Wildcard: "import com.example.*"
218
+ if imp.module:
219
+ wildcards.append(imp.module)
220
+ # Infer package from first class's qualified name
221
+ for cls in pf.classes:
222
+ qn = cls.qualified_name.full
223
+ if "." in qn:
224
+ pkg = qn.rsplit(".", 1)[0]
225
+ break
226
+ self._file_imports[pf.path] = import_map
227
+ self._file_wildcards[pf.path] = wildcards
228
+ self._file_package[pf.path] = pkg
229
+
230
+ # -------------------------------------------------------------------------
231
+ # TypeResolver protocol
232
+ # -------------------------------------------------------------------------
233
+
234
+ def resolve_type(
235
+ self,
236
+ name: str,
237
+ in_file: Path | None = None,
238
+ ) -> ResolvedType | None:
239
+ """Resolve a type name to its definition."""
240
+ if not name or name in _JAVA_BUILTINS:
241
+ return None
242
+
243
+ # Strip array suffix ("User[]" → "User")
244
+ name = name.rstrip("[]").strip()
245
+
246
+ # 1. Direct qualified name lookup
247
+ if name in self._symbol_table:
248
+ return self._to_resolved_type(self._symbol_table[name])
249
+
250
+ # 2. Strip generics and retry ("List<User>" → "User", then resolve "User")
251
+ stripped = _strip_generics(name)
252
+ if stripped != name:
253
+ return self.resolve_type(stripped, in_file)
254
+
255
+ # 3. File-local import resolution
256
+ if in_file is not None:
257
+ import_map = self._file_imports.get(in_file, {})
258
+ qn = import_map.get(name)
259
+ if qn and qn in self._symbol_table:
260
+ return self._to_resolved_type(self._symbol_table[qn])
261
+
262
+ # 4. Wildcard package scan ("import com.example.*")
263
+ for pkg in self._file_wildcards.get(in_file, []):
264
+ candidate = f"{pkg}.{name}"
265
+ if candidate in self._symbol_table:
266
+ return self._to_resolved_type(self._symbol_table[candidate])
267
+
268
+ # 5. Same-package implicit visibility (no import needed in Java)
269
+ file_pkg = self._file_package.get(in_file, "")
270
+ if file_pkg:
271
+ candidate = f"{file_pkg}.{name}"
272
+ if candidate in self._symbol_table:
273
+ return self._to_resolved_type(self._symbol_table[candidate])
274
+
275
+ # 6. Project-wide simple name index (unambiguous cross-project lookup)
276
+ candidates = self._simple_name_index.get(name, [])
277
+ if len(candidates) == 1:
278
+ return self._to_resolved_type(self._symbol_table[candidates[0]])
279
+ if len(candidates) > 1:
280
+ logger.debug("Ambiguous type '%s': %s", name, candidates)
281
+ return self._to_resolved_type(self._symbol_table[candidates[0]])
282
+
283
+ return None
284
+
285
+ def is_model_type(
286
+ self,
287
+ name: str,
288
+ in_file: Path | None = None,
289
+ ) -> bool:
290
+ """Check if a type is a data model."""
291
+ resolved = self.resolve_type(name, in_file)
292
+ if resolved is None:
293
+ return False
294
+ return resolved.is_model
295
+
296
+ def get_model_fields(
297
+ self,
298
+ model_name: str,
299
+ in_file: Path | None = None,
300
+ include_inherited: bool = True,
301
+ ) -> list[ResolvedField]:
302
+ """Get all fields for a model, optionally including inherited."""
303
+ resolved = self.resolve_type(model_name, in_file)
304
+ if resolved is None:
305
+ return []
306
+ if resolved.definition is None:
307
+ return []
308
+
309
+ fields = list(resolved.fields)
310
+
311
+ if include_inherited:
312
+ for base_name in resolved.base_classes:
313
+ base = self.resolve_type(base_name, in_file)
314
+ if base and base.fields:
315
+ for f in base.fields:
316
+ if not any(ef.name == f.name for ef in fields):
317
+ fields.append(f)
318
+
319
+ return fields
320
+
321
+ def is_subclass_of(
322
+ self,
323
+ class_name: str,
324
+ base_name: str,
325
+ in_file: Path | None = None,
326
+ ) -> bool:
327
+ """Check if class_name inherits from base_name."""
328
+ resolved = self.resolve_type(class_name, in_file)
329
+ if resolved is None:
330
+ return False
331
+ if base_name in resolved.base_classes:
332
+ return True
333
+ return base_name in resolved.all_ancestors
334
+
335
+ def get_all_models(self) -> dict[str, ResolvedType]:
336
+ """Return all detected model types."""
337
+ result: dict[str, ResolvedType] = {}
338
+ for qname, cls in self._symbol_table.items():
339
+ rt = self._to_resolved_type(cls)
340
+ if rt.is_model:
341
+ result[qname] = rt
342
+ return result
343
+
344
+ # -------------------------------------------------------------------------
345
+ # Helpers
346
+ # -------------------------------------------------------------------------
347
+
348
+ def _to_resolved_type(self, cls: ParsedClass) -> ResolvedType:
349
+ """Convert a ParsedClass to a ResolvedType."""
350
+ qname = cls.qualified_name.full
351
+ annotation_names = {d.name for d in cls.decorators}
352
+
353
+ is_model = bool(annotation_names & _MODEL_ANNOTATIONS) or self._has_model_fields(cls)
354
+ is_enum = cls.is_enum
355
+
356
+ all_ancestors = self._collect_ancestors(cls.base_classes)
357
+
358
+ resolved_fields: list[ResolvedField] = [
359
+ ResolvedField(
360
+ name=f.name,
361
+ type_annotation=f.type_annotation,
362
+ default_value=f.default_value,
363
+ is_required=f.default_value is None,
364
+ defined_in_class=qname,
365
+ )
366
+ for f in cls.fields
367
+ ]
368
+
369
+ return ResolvedType(
370
+ name=cls.name,
371
+ qualified_name=qname,
372
+ file_path=cls.location.file if cls.location else None,
373
+ is_class=True,
374
+ is_model=is_model,
375
+ is_enum=is_enum,
376
+ base_classes=list(cls.base_classes),
377
+ all_ancestors=all_ancestors,
378
+ fields=resolved_fields,
379
+ definition=cls,
380
+ )
381
+
382
+ def _has_model_fields(self, cls: ParsedClass) -> bool:
383
+ """Heuristic: classes with multiple typed fields are likely models."""
384
+ return len(cls.fields) >= 2
385
+
386
+ def _collect_ancestors(self, base_names: list[str]) -> list[str]:
387
+ """Recursively collect all ancestor class names.
388
+
389
+ Uses a cache and direct dict lookup (O(1) per step) rather than
390
+ the original O(n) linear scan of the symbol table per ancestor.
391
+ """
392
+ cache_key = ",".join(sorted(base_names))
393
+ if cache_key in self._ancestor_cache:
394
+ return list(self._ancestor_cache[cache_key])
395
+
396
+ ancestors: list[str] = []
397
+ seen: set[str] = set()
398
+ queue = list(base_names)
399
+
400
+ # Build a reverse index: simple_name → qualified_name for fast lookup
401
+ # (reuse _simple_name_index which is already built)
402
+ while queue:
403
+ name = queue.pop(0)
404
+ if name in seen:
405
+ continue
406
+ seen.add(name)
407
+ ancestors.append(name)
408
+
409
+ # Fast lookup: try direct qname first, then simple name index
410
+ cls = self._symbol_table.get(name)
411
+ if cls is None:
412
+ candidates = self._simple_name_index.get(name, [])
413
+ if candidates:
414
+ cls = self._symbol_table.get(candidates[0])
415
+
416
+ if cls is not None:
417
+ for base in cls.base_classes:
418
+ if base not in seen:
419
+ queue.append(base)
420
+
421
+ self._ancestor_cache[cache_key] = ancestors
422
+ return ancestors
@@ -0,0 +1,150 @@
1
+ """Python parsing using LibCST."""
2
+
3
+ from .cbv_extractor import (
4
+ CBVClass,
5
+ CBVExtractor,
6
+ CBVRoute,
7
+ extract_cbv_routes,
8
+ )
9
+ from .constant_resolver import (
10
+ ConfigClass,
11
+ ConstantResolver,
12
+ ResolvedConstant,
13
+ build_constant_resolver,
14
+ )
15
+ from .cross_file_resolver import (
16
+ CrossFileResolver,
17
+ ImportGraph,
18
+ ResolvedImport,
19
+ ResolvedSymbol,
20
+ build_cross_file_resolver,
21
+ detect_src_layout,
22
+ infer_module_path,
23
+ )
24
+ from .dynamic_route_detector import (
25
+ DynamicRoute,
26
+ DynamicRouteDetector,
27
+ RouteFactory,
28
+ detect_dynamic_routes,
29
+ )
30
+ from .language_services import (
31
+ PythonConstantResolverAdapter,
32
+ PythonLanguageServices,
33
+ PythonPathResolverAdapter,
34
+ PythonRouterRegistryAdapter,
35
+ PythonTypeResolverAdapter,
36
+ get_python_language_services,
37
+ )
38
+ from .parameter_analyzer import (
39
+ AnalyzedParameter,
40
+ DefaultValueAnalyzer,
41
+ ParameterAnalyzer,
42
+ TypeAnnotationAnalyzer,
43
+ extract_path_params,
44
+ )
45
+ from .parser import PythonParser, PythonProjectParser, get_python_parser
46
+ from .path_resolver import (
47
+ PathResolver,
48
+ ResolvedPath,
49
+ TrackedVariable,
50
+ resolve_route_path,
51
+ )
52
+ from .router_registry import (
53
+ RouterDefinition,
54
+ RouterInclusion,
55
+ RouterRegistry,
56
+ RouterTree,
57
+ build_router_registry,
58
+ )
59
+ from .type_resolver import (
60
+ ResolvedField,
61
+ ResolvedModel,
62
+ ResolvedType,
63
+ SchemaBuilder,
64
+ TypeAnnotationParser,
65
+ TypeResolver,
66
+ )
67
+ from .visitors import (
68
+ ExtractedArgument,
69
+ ExtractedAssignment,
70
+ ExtractedCall,
71
+ ExtractedClass,
72
+ ExtractedDecorator,
73
+ ExtractedField,
74
+ ExtractedFunction,
75
+ ExtractedImport,
76
+ ExtractedParameter,
77
+ PythonExtractor,
78
+ )
79
+
80
+ __all__ = [
81
+ # Parser
82
+ "PythonParser",
83
+ "PythonProjectParser",
84
+ "get_python_parser",
85
+ # Visitor
86
+ "PythonExtractor",
87
+ "ExtractedFunction",
88
+ "ExtractedClass",
89
+ "ExtractedImport",
90
+ "ExtractedCall",
91
+ "ExtractedArgument",
92
+ "ExtractedAssignment",
93
+ "ExtractedDecorator",
94
+ "ExtractedParameter",
95
+ "ExtractedField",
96
+ # Type Resolution
97
+ "TypeResolver",
98
+ "TypeAnnotationParser",
99
+ "SchemaBuilder",
100
+ "ResolvedType",
101
+ "ResolvedField",
102
+ "ResolvedModel",
103
+ # Cross-file Resolution
104
+ "CrossFileResolver",
105
+ "build_cross_file_resolver",
106
+ "ResolvedSymbol",
107
+ "ResolvedImport",
108
+ "ImportGraph",
109
+ "infer_module_path",
110
+ "detect_src_layout",
111
+ # Parameter Analysis
112
+ "ParameterAnalyzer",
113
+ "AnalyzedParameter",
114
+ "extract_path_params",
115
+ "DefaultValueAnalyzer",
116
+ "TypeAnnotationAnalyzer",
117
+ # Router Registry
118
+ "RouterRegistry",
119
+ "RouterDefinition",
120
+ "RouterInclusion",
121
+ "RouterTree",
122
+ "build_router_registry",
123
+ # Path Resolution
124
+ "PathResolver",
125
+ "ResolvedPath",
126
+ "TrackedVariable",
127
+ "resolve_route_path",
128
+ # Dynamic Route Detection
129
+ "DynamicRouteDetector",
130
+ "DynamicRoute",
131
+ "RouteFactory",
132
+ "detect_dynamic_routes",
133
+ # Class-Based Views
134
+ "CBVExtractor",
135
+ "CBVClass",
136
+ "CBVRoute",
137
+ "extract_cbv_routes",
138
+ # Constant Resolution
139
+ "ConstantResolver",
140
+ "ResolvedConstant",
141
+ "ConfigClass",
142
+ "build_constant_resolver",
143
+ # Language Services
144
+ "PythonLanguageServices",
145
+ "PythonTypeResolverAdapter",
146
+ "PythonConstantResolverAdapter",
147
+ "PythonPathResolverAdapter",
148
+ "PythonRouterRegistryAdapter",
149
+ "get_python_language_services",
150
+ ]