Navegador

navegador / navegador / enrichment / fastapi.py
Blame History Raw 218 lines
1
"""
2
FastAPI framework enricher.
3
4
Promotes generic Function/Class nodes to FastAPI semantic types:
5
- Route — functions decorated with @app.<method> or @router.<method>
6
- Dependency — functions with Depends( in their signature
7
- PydanticModel — classes that inherit from BaseModel
8
- BackgroundTask — functions decorated with @app.on_event or using BackgroundTasks
9
"""
10
11
from navegador.enrichment.base import EnrichmentResult, FrameworkEnricher
12
from navegador.graph.store import GraphStore
13
14
# HTTP verbs recognised as route decorators
15
_HTTP_METHODS = ("get", "post", "put", "delete", "patch", "head", "options")
16
17
18
class FastAPIEnricher(FrameworkEnricher):
19
"""Enriches a navegador graph with FastAPI-specific semantic types."""
20
21
def __init__(self, store: GraphStore) -> None:
22
super().__init__(store)
23
24
# ── Identity ──────────────────────────────────────────────────────────────
25
26
@property
27
def framework_name(self) -> str:
28
return "fastapi"
29
30
@property
31
def detection_patterns(self) -> list[str]:
32
return ["fastapi"]
33
34
# ── Enrichment ────────────────────────────────────────────────────────────
35
36
def enrich(self) -> EnrichmentResult:
37
result = EnrichmentResult()
38
39
routes = self._enrich_routes()
40
result.promoted += routes
41
result.patterns_found["routes"] = routes
42
43
dependencies = self._enrich_dependencies()
44
result.promoted += dependencies
45
result.patterns_found["dependencies"] = dependencies
46
47
pydantic_models = self._enrich_pydantic_models()
48
result.promoted += pydantic_models
49
result.patterns_found["pydantic_models"] = pydantic_models
50
51
background_tasks = self._enrich_background_tasks()
52
result.promoted += background_tasks
53
result.patterns_found["background_tasks"] = background_tasks
54
55
return result
56
57
# ── Pattern helpers ───────────────────────────────────────────────────────
58
59
def _enrich_routes(self) -> int:
60
"""
61
Find Function/Method nodes linked to Decorator nodes whose names match
62
@app.<method> or @router.<method> patterns (e.g. app.get, router.post).
63
64
Falls back to querying the ``signature`` and ``docstring`` properties for
65
route decorator patterns when no Decorator nodes are present in the graph.
66
"""
67
promoted = 0
68
69
# Strategy 1: Decorator nodes connected via DECORATES edges
70
for http_method in _HTTP_METHODS:
71
result = self.store.query(
72
"MATCH (d:Decorator)-[:DECORATES]->(n) "
73
"WHERE d.name CONTAINS $pattern "
74
"RETURN n.name, n.file_path",
75
{"pattern": f".{http_method}"},
76
)
77
rows = result.result_set or []
78
for row in rows:
79
name, file_path = row[0], row[1]
80
if name and file_path:
81
self._promote_node(name, file_path, "Route", {"http_method": http_method})
82
promoted += 1
83
84
# Strategy 2: signature / docstring heuristics (no Decorator nodes)
85
for http_method in _HTTP_METHODS:
86
for prop in ("signature", "docstring"):
87
result = self.store.query(
88
f"MATCH (n) WHERE (n:Function OR n:Method) "
89
f"AND n.{prop} IS NOT NULL "
90
f"AND n.{prop} CONTAINS $pattern "
91
"RETURN n.name, n.file_path",
92
{"pattern": f".{http_method}("},
93
)
94
rows = result.result_set or []
95
for row in rows:
96
name, file_path = row[0], row[1]
97
if name and file_path:
98
self._promote_node(name, file_path, "Route", {"http_method": http_method})
99
promoted += 1
100
101
return promoted
102
103
def _enrich_dependencies(self) -> int:
104
"""
105
Find Function/Method nodes whose signature contains ``Depends(``.
106
These are FastAPI dependency-injection callables.
107
"""
108
promoted = 0
109
110
for prop in ("signature", "docstring"):
111
result = self.store.query(
112
f"MATCH (n) WHERE (n:Function OR n:Method) "
113
f"AND n.{prop} IS NOT NULL "
114
f"AND n.{prop} CONTAINS $pattern "
115
"RETURN n.name, n.file_path",
116
{"pattern": "Depends("},
117
)
118
rows = result.result_set or []
119
for row in rows:
120
name, file_path = row[0], row[1]
121
if name and file_path:
122
self._promote_node(name, file_path, "Dependency")
123
promoted += 1
124
125
# Also match via Decorator nodes named "Depends"
126
result = self.store.query(
127
"MATCH (d:Decorator)-[:DECORATES]->(n) "
128
"WHERE d.name CONTAINS $pattern "
129
"RETURN n.name, n.file_path",
130
{"pattern": "Depends"},
131
)
132
rows = result.result_set or []
133
for row in rows:
134
name, file_path = row[0], row[1]
135
if name and file_path:
136
self._promote_node(name, file_path, "Dependency")
137
promoted += 1
138
139
return promoted
140
141
def _enrich_pydantic_models(self) -> int:
142
"""
143
Find Class nodes that inherit from ``BaseModel`` via INHERITS edges,
144
or whose name appears as a base in the raw INHERITS graph.
145
"""
146
promoted = 0
147
148
# Primary: INHERITS edges pointing to a node named "BaseModel"
149
result = self.store.query(
150
"MATCH (n:Class)-[:INHERITS]->(base) "
151
"WHERE base.name = $base_name "
152
"RETURN n.name, n.file_path",
153
{"base_name": "BaseModel"},
154
)
155
rows = result.result_set or []
156
for row in rows:
157
name, file_path = row[0], row[1]
158
if name and file_path:
159
self._promote_node(name, file_path, "PydanticModel")
160
promoted += 1
161
162
# Fallback: docstring or source mentions BaseModel
163
result = self.store.query(
164
"MATCH (n:Class) "
165
"WHERE n.docstring IS NOT NULL AND n.docstring CONTAINS $pattern "
166
"RETURN n.name, n.file_path",
167
{"pattern": "BaseModel"},
168
)
169
rows = result.result_set or []
170
for row in rows:
171
name, file_path = row[0], row[1]
172
if name and file_path:
173
self._promote_node(name, file_path, "PydanticModel")
174
promoted += 1
175
176
return promoted
177
178
def _enrich_background_tasks(self) -> int:
179
"""
180
Find Function/Method nodes that:
181
- are decorated with @app.on_event (via Decorator nodes or signature), or
182
- reference ``BackgroundTasks`` in their signature / docstring.
183
"""
184
promoted = 0
185
186
# Strategy 1: Decorator nodes matching on_event
187
result = self.store.query(
188
"MATCH (d:Decorator)-[:DECORATES]->(n) "
189
"WHERE d.name CONTAINS $pattern "
190
"RETURN n.name, n.file_path",
191
{"pattern": "on_event"},
192
)
193
rows = result.result_set or []
194
for row in rows:
195
name, file_path = row[0], row[1]
196
if name and file_path:
197
self._promote_node(name, file_path, "BackgroundTask")
198
promoted += 1
199
200
# Strategy 2: signature / docstring heuristics for on_event and BackgroundTasks
201
for pattern in ("on_event(", "BackgroundTasks"):
202
for prop in ("signature", "docstring"):
203
result = self.store.query(
204
f"MATCH (n) WHERE (n:Function OR n:Method) "
205
f"AND n.{prop} IS NOT NULL "
206
f"AND n.{prop} CONTAINS $pattern "
207
"RETURN n.name, n.file_path",
208
{"pattern": pattern},
209
)
210
rows = result.result_set or []
211
for row in rows:
212
name, file_path = row[0], row[1]
213
if name and file_path:
214
self._promote_node(name, file_path, "BackgroundTask")
215
promoted += 1
216
217
return promoted
218

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button