osscodeiq 0.0.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 (183) hide show
  1. osscodeiq/__init__.py +0 -0
  2. osscodeiq/analyzer.py +467 -0
  3. osscodeiq/cache/__init__.py +0 -0
  4. osscodeiq/cache/hasher.py +23 -0
  5. osscodeiq/cache/store.py +300 -0
  6. osscodeiq/classifiers/__init__.py +0 -0
  7. osscodeiq/classifiers/layer_classifier.py +69 -0
  8. osscodeiq/cli.py +721 -0
  9. osscodeiq/config.py +113 -0
  10. osscodeiq/detectors/__init__.py +0 -0
  11. osscodeiq/detectors/auth/__init__.py +0 -0
  12. osscodeiq/detectors/auth/certificate_auth.py +139 -0
  13. osscodeiq/detectors/auth/ldap_auth.py +89 -0
  14. osscodeiq/detectors/auth/session_header_auth.py +120 -0
  15. osscodeiq/detectors/base.py +41 -0
  16. osscodeiq/detectors/config/__init__.py +0 -0
  17. osscodeiq/detectors/config/batch_structure.py +128 -0
  18. osscodeiq/detectors/config/cloudformation.py +183 -0
  19. osscodeiq/detectors/config/docker_compose.py +179 -0
  20. osscodeiq/detectors/config/github_actions.py +150 -0
  21. osscodeiq/detectors/config/gitlab_ci.py +216 -0
  22. osscodeiq/detectors/config/helm_chart.py +187 -0
  23. osscodeiq/detectors/config/ini_structure.py +101 -0
  24. osscodeiq/detectors/config/json_structure.py +72 -0
  25. osscodeiq/detectors/config/kubernetes.py +305 -0
  26. osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
  27. osscodeiq/detectors/config/openapi.py +194 -0
  28. osscodeiq/detectors/config/package_json.py +99 -0
  29. osscodeiq/detectors/config/properties_detector.py +108 -0
  30. osscodeiq/detectors/config/pyproject_toml.py +169 -0
  31. osscodeiq/detectors/config/sql_structure.py +155 -0
  32. osscodeiq/detectors/config/toml_structure.py +93 -0
  33. osscodeiq/detectors/config/tsconfig_json.py +105 -0
  34. osscodeiq/detectors/config/yaml_structure.py +82 -0
  35. osscodeiq/detectors/cpp/__init__.py +0 -0
  36. osscodeiq/detectors/cpp/cpp_structures.py +192 -0
  37. osscodeiq/detectors/csharp/__init__.py +0 -0
  38. osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
  39. osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
  40. osscodeiq/detectors/csharp/csharp_structures.py +317 -0
  41. osscodeiq/detectors/docs/__init__.py +0 -0
  42. osscodeiq/detectors/docs/markdown_structure.py +117 -0
  43. osscodeiq/detectors/frontend/__init__.py +0 -0
  44. osscodeiq/detectors/frontend/angular_components.py +177 -0
  45. osscodeiq/detectors/frontend/frontend_routes.py +259 -0
  46. osscodeiq/detectors/frontend/react_components.py +148 -0
  47. osscodeiq/detectors/frontend/svelte_components.py +84 -0
  48. osscodeiq/detectors/frontend/vue_components.py +150 -0
  49. osscodeiq/detectors/generic/__init__.py +1 -0
  50. osscodeiq/detectors/generic/imports_detector.py +413 -0
  51. osscodeiq/detectors/go/__init__.py +0 -0
  52. osscodeiq/detectors/go/go_orm.py +202 -0
  53. osscodeiq/detectors/go/go_structures.py +162 -0
  54. osscodeiq/detectors/go/go_web.py +157 -0
  55. osscodeiq/detectors/iac/__init__.py +0 -0
  56. osscodeiq/detectors/iac/bicep.py +135 -0
  57. osscodeiq/detectors/iac/dockerfile.py +182 -0
  58. osscodeiq/detectors/iac/terraform.py +188 -0
  59. osscodeiq/detectors/java/__init__.py +0 -0
  60. osscodeiq/detectors/java/azure_functions.py +424 -0
  61. osscodeiq/detectors/java/azure_messaging.py +350 -0
  62. osscodeiq/detectors/java/class_hierarchy.py +349 -0
  63. osscodeiq/detectors/java/config_def.py +82 -0
  64. osscodeiq/detectors/java/cosmos_db.py +105 -0
  65. osscodeiq/detectors/java/graphql_resolver.py +188 -0
  66. osscodeiq/detectors/java/grpc_service.py +142 -0
  67. osscodeiq/detectors/java/ibm_mq.py +178 -0
  68. osscodeiq/detectors/java/jaxrs.py +160 -0
  69. osscodeiq/detectors/java/jdbc.py +196 -0
  70. osscodeiq/detectors/java/jms.py +116 -0
  71. osscodeiq/detectors/java/jpa_entity.py +143 -0
  72. osscodeiq/detectors/java/kafka.py +113 -0
  73. osscodeiq/detectors/java/kafka_protocol.py +70 -0
  74. osscodeiq/detectors/java/micronaut.py +248 -0
  75. osscodeiq/detectors/java/module_deps.py +191 -0
  76. osscodeiq/detectors/java/public_api.py +206 -0
  77. osscodeiq/detectors/java/quarkus.py +176 -0
  78. osscodeiq/detectors/java/rabbitmq.py +150 -0
  79. osscodeiq/detectors/java/raw_sql.py +136 -0
  80. osscodeiq/detectors/java/repository.py +131 -0
  81. osscodeiq/detectors/java/rmi.py +129 -0
  82. osscodeiq/detectors/java/spring_events.py +117 -0
  83. osscodeiq/detectors/java/spring_rest.py +168 -0
  84. osscodeiq/detectors/java/spring_security.py +212 -0
  85. osscodeiq/detectors/java/tibco_ems.py +193 -0
  86. osscodeiq/detectors/java/websocket.py +188 -0
  87. osscodeiq/detectors/kotlin/__init__.py +0 -0
  88. osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
  89. osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
  90. osscodeiq/detectors/proto/__init__.py +0 -0
  91. osscodeiq/detectors/proto/proto_structure.py +153 -0
  92. osscodeiq/detectors/python/__init__.py +0 -0
  93. osscodeiq/detectors/python/celery_tasks.py +88 -0
  94. osscodeiq/detectors/python/django_auth.py +132 -0
  95. osscodeiq/detectors/python/django_models.py +157 -0
  96. osscodeiq/detectors/python/django_views.py +74 -0
  97. osscodeiq/detectors/python/fastapi_auth.py +143 -0
  98. osscodeiq/detectors/python/fastapi_routes.py +68 -0
  99. osscodeiq/detectors/python/flask_routes.py +67 -0
  100. osscodeiq/detectors/python/kafka_python.py +175 -0
  101. osscodeiq/detectors/python/pydantic_models.py +115 -0
  102. osscodeiq/detectors/python/python_structures.py +234 -0
  103. osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
  104. osscodeiq/detectors/registry.py +100 -0
  105. osscodeiq/detectors/rust/__init__.py +0 -0
  106. osscodeiq/detectors/rust/actix_web.py +234 -0
  107. osscodeiq/detectors/rust/rust_structures.py +174 -0
  108. osscodeiq/detectors/scala/__init__.py +0 -0
  109. osscodeiq/detectors/scala/scala_structures.py +128 -0
  110. osscodeiq/detectors/shell/__init__.py +0 -0
  111. osscodeiq/detectors/shell/bash_detector.py +127 -0
  112. osscodeiq/detectors/shell/powershell_detector.py +118 -0
  113. osscodeiq/detectors/typescript/__init__.py +0 -0
  114. osscodeiq/detectors/typescript/express_routes.py +55 -0
  115. osscodeiq/detectors/typescript/fastify_routes.py +156 -0
  116. osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
  117. osscodeiq/detectors/typescript/kafka_js.py +164 -0
  118. osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
  119. osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
  120. osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
  121. osscodeiq/detectors/typescript/passport_jwt.py +133 -0
  122. osscodeiq/detectors/typescript/prisma_orm.py +96 -0
  123. osscodeiq/detectors/typescript/remix_routes.py +160 -0
  124. osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
  125. osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
  126. osscodeiq/detectors/typescript/typescript_structures.py +185 -0
  127. osscodeiq/detectors/utils.py +49 -0
  128. osscodeiq/discovery/__init__.py +11 -0
  129. osscodeiq/discovery/change_detector.py +97 -0
  130. osscodeiq/discovery/file_discovery.py +342 -0
  131. osscodeiq/flow/__init__.py +0 -0
  132. osscodeiq/flow/engine.py +78 -0
  133. osscodeiq/flow/models.py +72 -0
  134. osscodeiq/flow/renderer.py +127 -0
  135. osscodeiq/flow/templates/interactive.html +252 -0
  136. osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
  137. osscodeiq/flow/vendor/cytoscape.min.js +32 -0
  138. osscodeiq/flow/vendor/dagre.min.js +3809 -0
  139. osscodeiq/flow/views.py +357 -0
  140. osscodeiq/graph/__init__.py +0 -0
  141. osscodeiq/graph/backend.py +52 -0
  142. osscodeiq/graph/backends/__init__.py +23 -0
  143. osscodeiq/graph/backends/kuzu.py +576 -0
  144. osscodeiq/graph/backends/networkx.py +135 -0
  145. osscodeiq/graph/backends/sqlite_backend.py +406 -0
  146. osscodeiq/graph/builder.py +297 -0
  147. osscodeiq/graph/query.py +228 -0
  148. osscodeiq/graph/store.py +183 -0
  149. osscodeiq/graph/views.py +231 -0
  150. osscodeiq/models/__init__.py +17 -0
  151. osscodeiq/models/graph.py +116 -0
  152. osscodeiq/output/__init__.py +0 -0
  153. osscodeiq/output/dot.py +171 -0
  154. osscodeiq/output/mermaid.py +160 -0
  155. osscodeiq/output/safety.py +58 -0
  156. osscodeiq/output/serializers.py +42 -0
  157. osscodeiq/parsing/__init__.py +5 -0
  158. osscodeiq/parsing/languages/__init__.py +0 -0
  159. osscodeiq/parsing/languages/base.py +23 -0
  160. osscodeiq/parsing/languages/java.py +68 -0
  161. osscodeiq/parsing/languages/python.py +57 -0
  162. osscodeiq/parsing/languages/typescript.py +95 -0
  163. osscodeiq/parsing/parser_manager.py +125 -0
  164. osscodeiq/parsing/structured/__init__.py +0 -0
  165. osscodeiq/parsing/structured/gradle_parser.py +78 -0
  166. osscodeiq/parsing/structured/json_parser.py +24 -0
  167. osscodeiq/parsing/structured/properties_parser.py +56 -0
  168. osscodeiq/parsing/structured/sql_parser.py +54 -0
  169. osscodeiq/parsing/structured/xml_parser.py +148 -0
  170. osscodeiq/parsing/structured/yaml_parser.py +38 -0
  171. osscodeiq/server/__init__.py +7 -0
  172. osscodeiq/server/app.py +53 -0
  173. osscodeiq/server/mcp_server.py +174 -0
  174. osscodeiq/server/middleware.py +16 -0
  175. osscodeiq/server/routes.py +184 -0
  176. osscodeiq/server/service.py +445 -0
  177. osscodeiq/server/templates/welcome.html +56 -0
  178. osscodeiq-0.0.0.dist-info/METADATA +30 -0
  179. osscodeiq-0.0.0.dist-info/RECORD +183 -0
  180. osscodeiq-0.0.0.dist-info/WHEEL +5 -0
  181. osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
  182. osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
  183. osscodeiq-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,157 @@
1
+ """Django model detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class DjangoModelDetector:
13
+ """Detects Django model, manager, and relationship definitions."""
14
+
15
+ name: str = "python.django_models"
16
+ supported_languages: tuple[str, ...] = ("python",)
17
+
18
+ _DJANGO_MODEL_RE = re.compile(
19
+ r"^class\s+(\w+)\s*\(\s*[\w.]*Model\s*\)", re.MULTILINE
20
+ )
21
+ _FK_RE = re.compile(
22
+ r"(\w+)\s*=\s*models\.(?:ForeignKey|OneToOneField)\s*\(\s*[\"']?(\w+)",
23
+ re.MULTILINE,
24
+ )
25
+ _M2M_RE = re.compile(
26
+ r"(\w+)\s*=\s*models\.ManyToManyField\s*\(\s*[\"']?(\w+)", re.MULTILINE
27
+ )
28
+ _FIELD_RE = re.compile(r"(\w+)\s*=\s*models\.(\w+Field)\s*\(", re.MULTILINE)
29
+ _META_TABLE_RE = re.compile(r"db_table\s*=\s*[\"'](\w+)[\"']")
30
+ _META_ORDERING_RE = re.compile(r"ordering\s*=\s*(\[.*?\])")
31
+ _MANAGER_RE = re.compile(
32
+ r"^class\s+(\w+)\s*\(\s*[\w.]*Manager\s*\)", re.MULTILINE
33
+ )
34
+ _MANAGER_ASSIGNMENT_RE = re.compile(r"(\w+)\s*=\s*(\w+)\s*\(\s*\)", re.MULTILINE)
35
+
36
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
37
+ result = DetectorResult()
38
+ text = decode_text(ctx)
39
+
40
+ # Detect managers first so we can link them
41
+ manager_names: dict[str, str] = {}
42
+ for match in self._MANAGER_RE.finditer(text):
43
+ mgr_name = match.group(1)
44
+ line = text[: match.start()].count("\n") + 1
45
+ node_id = f"django:{ctx.file_path}:manager:{mgr_name}"
46
+ manager_names[mgr_name] = node_id
47
+ result.nodes.append(
48
+ GraphNode(
49
+ id=node_id,
50
+ kind=NodeKind.REPOSITORY,
51
+ label=mgr_name,
52
+ fqn=f"{ctx.file_path}::{mgr_name}",
53
+ module=ctx.module_name,
54
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
55
+ properties={"framework": "django", "type": "manager"},
56
+ )
57
+ )
58
+
59
+ # Detect models
60
+ for match in self._DJANGO_MODEL_RE.finditer(text):
61
+ class_name = match.group(1)
62
+ line = text[: match.start()].count("\n") + 1
63
+
64
+ # Determine class body boundaries
65
+ class_start = match.start()
66
+ next_class = re.search(r"\nclass\s+\w+", text[match.end() :])
67
+ class_body = (
68
+ text[class_start : match.end() + next_class.start()]
69
+ if next_class
70
+ else text[class_start:]
71
+ )
72
+
73
+ # Extract fields
74
+ fields: dict[str, str] = {}
75
+ for fm in self._FIELD_RE.finditer(class_body):
76
+ fields[fm.group(1)] = fm.group(2)
77
+
78
+ # Extract Meta properties
79
+ table_name: str | None = None
80
+ ordering: str | None = None
81
+ meta_match = re.search(r"class\s+Meta\s*:", class_body)
82
+ if meta_match:
83
+ meta_start = meta_match.end()
84
+ meta_end = len(class_body)
85
+ for cm in re.finditer(r"\n\s{4}\S", class_body[meta_start:]):
86
+ meta_end = meta_start + cm.start()
87
+ break
88
+ meta_block = class_body[meta_start:meta_end]
89
+ table_match = self._META_TABLE_RE.search(meta_block)
90
+ if table_match:
91
+ table_name = table_match.group(1)
92
+ ordering_match = self._META_ORDERING_RE.search(meta_block)
93
+ if ordering_match:
94
+ ordering = ordering_match.group(1)
95
+
96
+ node_id = f"django:{ctx.file_path}:model:{class_name}"
97
+ props: dict = {
98
+ "fields": fields,
99
+ "framework": "django",
100
+ }
101
+ if table_name:
102
+ props["table_name"] = table_name
103
+ if ordering:
104
+ props["ordering"] = ordering
105
+
106
+ result.nodes.append(
107
+ GraphNode(
108
+ id=node_id,
109
+ kind=NodeKind.ENTITY,
110
+ label=class_name,
111
+ fqn=f"{ctx.file_path}::{class_name}",
112
+ module=ctx.module_name,
113
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
114
+ properties=props,
115
+ )
116
+ )
117
+
118
+ # FK / OneToOne edges
119
+ for fk in self._FK_RE.finditer(class_body):
120
+ target = fk.group(2)
121
+ target_id = f"django:{ctx.file_path}:model:{target}"
122
+ result.edges.append(
123
+ GraphEdge(
124
+ source=node_id,
125
+ target=target_id,
126
+ kind=EdgeKind.DEPENDS_ON,
127
+ label=fk.group(1),
128
+ )
129
+ )
130
+
131
+ # M2M edges
132
+ for m2m in self._M2M_RE.finditer(class_body):
133
+ target = m2m.group(2)
134
+ target_id = f"django:{ctx.file_path}:model:{target}"
135
+ result.edges.append(
136
+ GraphEdge(
137
+ source=node_id,
138
+ target=target_id,
139
+ kind=EdgeKind.DEPENDS_ON,
140
+ label=m2m.group(1),
141
+ )
142
+ )
143
+
144
+ # Manager assignments (objects = MyManager())
145
+ for ma in self._MANAGER_ASSIGNMENT_RE.finditer(class_body):
146
+ mgr_class = ma.group(2)
147
+ if mgr_class in manager_names:
148
+ result.edges.append(
149
+ GraphEdge(
150
+ source=node_id,
151
+ target=manager_names[mgr_class],
152
+ kind=EdgeKind.QUERIES,
153
+ label=ma.group(1),
154
+ )
155
+ )
156
+
157
+ return result
@@ -0,0 +1,74 @@
1
+ """Django URL pattern and view detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class DjangoViewDetector:
13
+ """Detects Django URL patterns, class-based views, and function views."""
14
+
15
+ name: str = "python.django_views"
16
+ supported_languages: tuple[str, ...] = ("python",)
17
+
18
+ # urlpatterns entries: path('api/users/', UserView.as_view(), name='user-list')
19
+ _URL_PATTERN = re.compile(
20
+ r"(?:path|re_path|url)\(\s*['\"]([^'\"]+)['\"]"
21
+ r"\s*,\s*(\w[\w.]*)"
22
+ )
23
+
24
+ # Class-based views: class UserView(APIView): or class UserViewSet(ModelViewSet):
25
+ _CBV_PATTERN = re.compile(
26
+ r"class\s+(\w+)\(([^)]*(?:View|ViewSet|Mixin)[^)]*)\):"
27
+ )
28
+
29
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
30
+ result = DetectorResult()
31
+ text = decode_text(ctx)
32
+
33
+ # Detect URL patterns (typically in urls.py)
34
+ if "urlpatterns" in text:
35
+ for match in self._URL_PATTERN.finditer(text):
36
+ path_pattern = match.group(1)
37
+ view_ref = match.group(2)
38
+ line = text[:match.start()].count("\n") + 1
39
+
40
+ node_id = f"endpoint:{ctx.module_name or ''}:ALL:{path_pattern}"
41
+ result.nodes.append(GraphNode(
42
+ id=node_id,
43
+ kind=NodeKind.ENDPOINT,
44
+ label=f"{path_pattern}",
45
+ fqn=view_ref,
46
+ module=ctx.module_name,
47
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
48
+ properties={
49
+ "protocol": "REST",
50
+ "path_pattern": path_pattern,
51
+ "framework": "django",
52
+ "view_reference": view_ref,
53
+ },
54
+ ))
55
+
56
+ # Detect class-based views
57
+ for match in self._CBV_PATTERN.finditer(text):
58
+ class_name = match.group(1)
59
+ bases = match.group(2)
60
+ line = text[:match.start()].count("\n") + 1
61
+
62
+ node_id = f"class:{ctx.file_path}::{class_name}"
63
+ result.nodes.append(GraphNode(
64
+ id=node_id,
65
+ kind=NodeKind.CLASS,
66
+ label=class_name,
67
+ fqn=f"{ctx.file_path}::{class_name}",
68
+ module=ctx.module_name,
69
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
70
+ annotations=[f"extends:{bases.strip()}"],
71
+ properties={"framework": "django", "stereotype": "view"},
72
+ ))
73
+
74
+ return result
@@ -0,0 +1,143 @@
1
+ """FastAPI authentication and authorization detector for Python source files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import GraphNode, NodeKind, SourceLocation
10
+
11
+ # Depends(get_current_user) or Depends(get_current_active_user) etc.
12
+ _DEPENDS_AUTH_RE = re.compile(
13
+ r'Depends\(\s*(get_current[\w]*|require_auth[\w]*|auth[\w]*)\s*\)'
14
+ )
15
+
16
+ # Security(oauth2_scheme) or Security(some_auth_scheme, scopes=[...])
17
+ _SECURITY_RE = re.compile(
18
+ r'Security\(\s*(\w+)'
19
+ )
20
+
21
+ # HTTPBearer() instantiation
22
+ _HTTP_BEARER_RE = re.compile(
23
+ r'HTTPBearer\s*\('
24
+ )
25
+
26
+ # OAuth2PasswordBearer(tokenUrl=...) instantiation
27
+ _OAUTH2_PASSWORD_BEARER_RE = re.compile(
28
+ r'OAuth2PasswordBearer\s*\(\s*tokenUrl\s*=\s*["\']([^"\']*)["\']'
29
+ )
30
+
31
+ # HTTPBasic() instantiation
32
+ _HTTP_BASIC_RE = re.compile(
33
+ r'HTTPBasic\s*\('
34
+ )
35
+
36
+
37
+ def _line_number(text: str, pos: int) -> int:
38
+ """Return 1-based line number for a character offset."""
39
+ return text[:pos].count("\n") + 1
40
+
41
+
42
+ class FastAPIAuthDetector:
43
+ """Detects FastAPI auth patterns in Python source files."""
44
+
45
+ name: str = "fastapi_auth"
46
+ supported_languages: tuple[str, ...] = ("python",)
47
+
48
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
49
+ result = DetectorResult()
50
+ text = decode_text(ctx)
51
+
52
+ # Depends(get_current_user) and similar auth dependencies
53
+ for m in _DEPENDS_AUTH_RE.finditer(text):
54
+ line = _line_number(text, m.start())
55
+ dep_name = m.group(1)
56
+ result.nodes.append(GraphNode(
57
+ id=f"auth:{ctx.file_path}:Depends:{line}",
58
+ kind=NodeKind.GUARD,
59
+ label=f"Depends({dep_name})",
60
+ module=ctx.module_name,
61
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
62
+ annotations=[f"Depends({dep_name})"],
63
+ properties={
64
+ "auth_type": "fastapi",
65
+ "auth_flow": "oauth2",
66
+ "dependency": dep_name,
67
+ "auth_required": True,
68
+ },
69
+ ))
70
+
71
+ # Security(scheme) calls
72
+ for m in _SECURITY_RE.finditer(text):
73
+ line = _line_number(text, m.start())
74
+ scheme_name = m.group(1)
75
+ result.nodes.append(GraphNode(
76
+ id=f"auth:{ctx.file_path}:Security:{line}",
77
+ kind=NodeKind.GUARD,
78
+ label=f"Security({scheme_name})",
79
+ module=ctx.module_name,
80
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
81
+ annotations=[f"Security({scheme_name})"],
82
+ properties={
83
+ "auth_type": "fastapi",
84
+ "auth_flow": "oauth2",
85
+ "scheme": scheme_name,
86
+ "auth_required": True,
87
+ },
88
+ ))
89
+
90
+ # HTTPBearer() instantiations
91
+ for m in _HTTP_BEARER_RE.finditer(text):
92
+ line = _line_number(text, m.start())
93
+ result.nodes.append(GraphNode(
94
+ id=f"auth:{ctx.file_path}:HTTPBearer:{line}",
95
+ kind=NodeKind.GUARD,
96
+ label="HTTPBearer()",
97
+ module=ctx.module_name,
98
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
99
+ annotations=["HTTPBearer"],
100
+ properties={
101
+ "auth_type": "fastapi",
102
+ "auth_flow": "bearer",
103
+ "auth_required": True,
104
+ },
105
+ ))
106
+
107
+ # OAuth2PasswordBearer(tokenUrl=...) instantiations
108
+ for m in _OAUTH2_PASSWORD_BEARER_RE.finditer(text):
109
+ line = _line_number(text, m.start())
110
+ token_url = m.group(1)
111
+ result.nodes.append(GraphNode(
112
+ id=f"auth:{ctx.file_path}:OAuth2PasswordBearer:{line}",
113
+ kind=NodeKind.GUARD,
114
+ label=f"OAuth2PasswordBearer({token_url})",
115
+ module=ctx.module_name,
116
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
117
+ annotations=["OAuth2PasswordBearer"],
118
+ properties={
119
+ "auth_type": "fastapi",
120
+ "auth_flow": "oauth2",
121
+ "token_url": token_url,
122
+ "auth_required": True,
123
+ },
124
+ ))
125
+
126
+ # HTTPBasic() instantiations
127
+ for m in _HTTP_BASIC_RE.finditer(text):
128
+ line = _line_number(text, m.start())
129
+ result.nodes.append(GraphNode(
130
+ id=f"auth:{ctx.file_path}:HTTPBasic:{line}",
131
+ kind=NodeKind.GUARD,
132
+ label="HTTPBasic()",
133
+ module=ctx.module_name,
134
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
135
+ annotations=["HTTPBasic"],
136
+ properties={
137
+ "auth_type": "fastapi",
138
+ "auth_flow": "basic",
139
+ "auth_required": True,
140
+ },
141
+ ))
142
+
143
+ return result
@@ -0,0 +1,68 @@
1
+ """FastAPI route detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class FastAPIRouteDetector:
13
+ """Detects FastAPI route decorators (@app.get, @router.post, etc.)."""
14
+
15
+ name: str = "python.fastapi_routes"
16
+ supported_languages: tuple[str, ...] = ("python",)
17
+
18
+ _ROUTE_PATTERN = re.compile(
19
+ r"@(\w+)\.(get|post|put|delete|patch|options|head)\(\s*['\"]([^'\"]+)['\"]"
20
+ r".*?\)\s*\n(?:\s*async\s+)?def\s+(\w+)",
21
+ re.DOTALL,
22
+ )
23
+
24
+ # APIRouter prefix: router = APIRouter(prefix="/api/v1/users")
25
+ _ROUTER_PREFIX = re.compile(
26
+ r"(\w+)\s*=\s*APIRouter\(.*?prefix\s*=\s*['\"]([^'\"]+)['\"]",
27
+ re.DOTALL,
28
+ )
29
+
30
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
31
+ result = DetectorResult()
32
+ text = decode_text(ctx)
33
+
34
+ # Extract router prefixes
35
+ prefixes: dict[str, str] = {}
36
+ for match in self._ROUTER_PREFIX.finditer(text):
37
+ prefixes[match.group(1)] = match.group(2)
38
+
39
+ for match in self._ROUTE_PATTERN.finditer(text):
40
+ router_name = match.group(1)
41
+ method = match.group(2).upper()
42
+ path = match.group(3)
43
+ func_name = match.group(4)
44
+
45
+ # Prepend router prefix if known
46
+ prefix = prefixes.get(router_name, "")
47
+ full_path = prefix + path
48
+
49
+ line = text[:match.start()].count("\n") + 1
50
+
51
+ node_id = f"endpoint:{ctx.module_name or ''}:{method}:{full_path}"
52
+ result.nodes.append(GraphNode(
53
+ id=node_id,
54
+ kind=NodeKind.ENDPOINT,
55
+ label=f"{method} {full_path}",
56
+ fqn=f"{ctx.file_path}::{func_name}",
57
+ module=ctx.module_name,
58
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
59
+ properties={
60
+ "protocol": "REST",
61
+ "http_method": method,
62
+ "path_pattern": full_path,
63
+ "framework": "fastapi",
64
+ "router": router_name,
65
+ },
66
+ ))
67
+
68
+ return result
@@ -0,0 +1,67 @@
1
+ """Flask route detector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class FlaskRouteDetector:
13
+ """Detects Flask route decorators (@app.route, @blueprint.route)."""
14
+
15
+ name: str = "python.flask_routes"
16
+ supported_languages: tuple[str, ...] = ("python",)
17
+
18
+ # Matches @app.route('/path', methods=['GET', 'POST']) and @blueprint.route(...)
19
+ _ROUTE_PATTERN = re.compile(
20
+ r"@(\w+)\.(route)\(\s*['\"]([^'\"]+)['\"]"
21
+ r"(?:.*?methods\s*=\s*\[([^\]]+)\])?"
22
+ r".*?\)\s*\n\s*def\s+(\w+)",
23
+ re.DOTALL,
24
+ )
25
+
26
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
27
+ result = DetectorResult()
28
+ text = decode_text(ctx)
29
+
30
+ for match in self._ROUTE_PATTERN.finditer(text):
31
+ blueprint = match.group(1)
32
+ path = match.group(3)
33
+ methods_raw = match.group(4)
34
+ func_name = match.group(5)
35
+
36
+ methods = ["GET"]
37
+ if methods_raw:
38
+ methods = [m.strip().strip("'\"") for m in methods_raw.split(",")]
39
+
40
+ line = text[:match.start()].count("\n") + 1
41
+
42
+ for method in methods:
43
+ node_id = f"endpoint:{ctx.module_name or ''}:{method}:{path}"
44
+ result.nodes.append(GraphNode(
45
+ id=node_id,
46
+ kind=NodeKind.ENDPOINT,
47
+ label=f"{method} {path}",
48
+ fqn=f"{ctx.file_path}::{func_name}",
49
+ module=ctx.module_name,
50
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
51
+ properties={
52
+ "protocol": "REST",
53
+ "http_method": method,
54
+ "path_pattern": path,
55
+ "framework": "flask",
56
+ "blueprint": blueprint,
57
+ },
58
+ ))
59
+
60
+ class_id = f"class:{ctx.file_path}::{blueprint}"
61
+ result.edges.append(GraphEdge(
62
+ source=class_id,
63
+ target=node_id,
64
+ kind=EdgeKind.EXPOSES,
65
+ ))
66
+
67
+ return result
@@ -0,0 +1,175 @@
1
+ """Kafka producer/consumer detector for Python source files.
2
+
3
+ Detects usage of confluent-kafka, aiokafka, and kafka-python libraries.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import re
9
+
10
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
11
+ from osscodeiq.detectors.utils import decode_text
12
+ from osscodeiq.models.graph import (
13
+ EdgeKind,
14
+ GraphEdge,
15
+ GraphNode,
16
+ NodeKind,
17
+ SourceLocation,
18
+ )
19
+
20
+ # Producer instantiation patterns
21
+ _PRODUCER_RE = re.compile(
22
+ r"(KafkaProducer|AIOKafkaProducer)\s*\(", re.MULTILINE
23
+ )
24
+ _CONFLUENT_PRODUCER_RE = re.compile(
25
+ r"Producer\s*\(\s*\{", re.MULTILINE
26
+ )
27
+
28
+ # Consumer instantiation patterns
29
+ _CONSUMER_RE = re.compile(
30
+ r"(KafkaConsumer|AIOKafkaConsumer)\s*\(", re.MULTILINE
31
+ )
32
+ _CONFLUENT_CONSUMER_RE = re.compile(
33
+ r"Consumer\s*\(\s*\{", re.MULTILINE
34
+ )
35
+
36
+ # Topic send/produce patterns
37
+ _SEND_RE = re.compile(
38
+ r"\.send\s*\(\s*['\"]([^'\"]+)['\"]", re.MULTILINE
39
+ )
40
+ _PRODUCE_RE = re.compile(
41
+ r"\.produce\s*\(\s*['\"]([^'\"]+)['\"]", re.MULTILINE
42
+ )
43
+
44
+ # Subscribe pattern
45
+ _SUBSCRIBE_RE = re.compile(
46
+ r"\.subscribe\s*\(\s*\[\s*['\"]([^'\"]+)['\"]", re.MULTILINE
47
+ )
48
+
49
+ # Import patterns
50
+ _IMPORT_RE = re.compile(
51
+ r"(?:from|import)\s+(confluent_kafka|kafka|aiokafka)\b", re.MULTILINE
52
+ )
53
+
54
+
55
+ class KafkaPythonDetector:
56
+ """Detects Kafka usage in Python via confluent-kafka, aiokafka, and kafka-python."""
57
+
58
+ name: str = "kafka_python"
59
+ supported_languages: tuple[str, ...] = ("python",)
60
+
61
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
62
+ result = DetectorResult()
63
+ text = decode_text(ctx)
64
+ lines = text.split("\n")
65
+ fp = ctx.file_path
66
+
67
+ # Quick bail-out: check for any Kafka-related keyword
68
+ if not any(kw in text for kw in (
69
+ "KafkaProducer", "KafkaConsumer",
70
+ "AIOKafkaProducer", "AIOKafkaConsumer",
71
+ "confluent_kafka", "from kafka",
72
+ "import kafka", "Producer(", "Consumer(",
73
+ )):
74
+ return result
75
+
76
+ seen_topics: set[str] = set()
77
+
78
+ def _ensure_topic(topic: str, role: str, line: int) -> str:
79
+ topic_id = f"kafka_py:{fp}:topic:{topic}"
80
+ if topic not in seen_topics:
81
+ seen_topics.add(topic)
82
+ result.nodes.append(GraphNode(
83
+ id=topic_id,
84
+ kind=NodeKind.TOPIC,
85
+ label=f"kafka:{topic}",
86
+ module=ctx.module_name,
87
+ location=SourceLocation(file_path=fp, line_start=line),
88
+ properties={"broker": "kafka", "topic": topic, "role": role},
89
+ ))
90
+ return topic_id
91
+
92
+ file_node_id = f"kafka_py:{fp}"
93
+
94
+ # Detect producer instantiations
95
+ for i, line in enumerate(lines):
96
+ lineno = i + 1
97
+ if _PRODUCER_RE.search(line) or _CONFLUENT_PRODUCER_RE.search(line):
98
+ result.nodes.append(GraphNode(
99
+ id=f"kafka_py:{fp}:producer:{lineno}",
100
+ kind=NodeKind.TOPIC,
101
+ label="kafka:producer",
102
+ module=ctx.module_name,
103
+ location=SourceLocation(file_path=fp, line_start=lineno),
104
+ properties={"role": "producer"},
105
+ ))
106
+
107
+ # Detect consumer instantiations
108
+ for i, line in enumerate(lines):
109
+ lineno = i + 1
110
+ if _CONSUMER_RE.search(line) or _CONFLUENT_CONSUMER_RE.search(line):
111
+ result.nodes.append(GraphNode(
112
+ id=f"kafka_py:{fp}:consumer:{lineno}",
113
+ kind=NodeKind.TOPIC,
114
+ label="kafka:consumer",
115
+ module=ctx.module_name,
116
+ location=SourceLocation(file_path=fp, line_start=lineno),
117
+ properties={"role": "consumer"},
118
+ ))
119
+
120
+ # Detect producer.send / producer.produce -> PRODUCES edges
121
+ for i, line in enumerate(lines):
122
+ lineno = i + 1
123
+ m = _SEND_RE.search(line)
124
+ if m and ("send" in line):
125
+ topic = m.group(1)
126
+ topic_id = _ensure_topic(topic, "producer", lineno)
127
+ result.edges.append(GraphEdge(
128
+ source=file_node_id,
129
+ target=topic_id,
130
+ kind=EdgeKind.PRODUCES,
131
+ label=f"produces to {topic}",
132
+ properties={"topic": topic},
133
+ ))
134
+ continue
135
+ m = _PRODUCE_RE.search(line)
136
+ if m:
137
+ topic = m.group(1)
138
+ topic_id = _ensure_topic(topic, "producer", lineno)
139
+ result.edges.append(GraphEdge(
140
+ source=file_node_id,
141
+ target=topic_id,
142
+ kind=EdgeKind.PRODUCES,
143
+ label=f"produces to {topic}",
144
+ properties={"topic": topic},
145
+ ))
146
+
147
+ # Detect consumer.subscribe -> CONSUMES edges
148
+ for i, line in enumerate(lines):
149
+ lineno = i + 1
150
+ m = _SUBSCRIBE_RE.search(line)
151
+ if m:
152
+ topic = m.group(1)
153
+ topic_id = _ensure_topic(topic, "consumer", lineno)
154
+ result.edges.append(GraphEdge(
155
+ source=file_node_id,
156
+ target=topic_id,
157
+ kind=EdgeKind.CONSUMES,
158
+ label=f"consumes from {topic}",
159
+ properties={"topic": topic},
160
+ ))
161
+
162
+ # Detect imports -> IMPORTS edges
163
+ for i, line in enumerate(lines):
164
+ m = _IMPORT_RE.search(line)
165
+ if m:
166
+ lib = m.group(1)
167
+ result.edges.append(GraphEdge(
168
+ source=file_node_id,
169
+ target=f"kafka_py:lib:{lib}",
170
+ kind=EdgeKind.IMPORTS,
171
+ label=f"imports {lib}",
172
+ properties={"library": lib},
173
+ ))
174
+
175
+ return result