@gate-forge/pack-fastapi 0.7.1 → 0.8.0
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.
- package/README.md +39 -0
- package/dist/detector.d.ts.map +1 -1
- package/dist/detector.js +5 -1
- package/dist/detector.js.map +1 -1
- package/dist/facts.d.ts.map +1 -1
- package/dist/facts.js +10 -0
- package/dist/facts.js.map +1 -1
- package/package.json +4 -4
- package/python/gateforge_fastapi_detector/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/gateforge_fastapi_detector/__pycache__/__init__.cpython-314.pyc +0 -0
- package/python/gateforge_fastapi_detector/__pycache__/__main__.cpython-312.pyc +0 -0
- package/python/gateforge_fastapi_detector/__pycache__/__main__.cpython-314.pyc +0 -0
- package/python/gateforge_fastapi_detector/__pycache__/scan.cpython-312.pyc +0 -0
- package/python/gateforge_fastapi_detector/__pycache__/scan.cpython-314.pyc +0 -0
- package/python/gateforge_fastapi_detector/scan.py +263 -18
package/README.md
CHANGED
|
@@ -145,6 +145,45 @@ handler names these very facts carry, and blocks ambiguity with typed
|
|
|
145
145
|
detection remains for genuinely stale authority signals (declaration
|
|
146
146
|
markers, adapter bindings, read-only declarations).
|
|
147
147
|
|
|
148
|
+
## Response-model wire names
|
|
149
|
+
|
|
150
|
+
Each server-route fact carries `attributes.responseModelFields`: the wire
|
|
151
|
+
names its response model answers to, in declaration order — every pydantic
|
|
152
|
+
field name plus every `Field(alias=...)` it declares, including the fields
|
|
153
|
+
it inherits from a base class the scanned set also proves. The model is
|
|
154
|
+
the decorator's `response_model=` when it declares one and the handler's
|
|
155
|
+
return annotation otherwise (FastAPI's own default), with the containers
|
|
156
|
+
FastAPI unwraps peeled off: `list[InvoiceOut]`, `Optional[MoneyOut]` and
|
|
157
|
+
`MoneyOut | None` all report the element model.
|
|
158
|
+
|
|
159
|
+
`detail` is always among the names: FastAPI answers a failed request with
|
|
160
|
+
`{"detail": ...}` (an `HTTPException`) or a `detail` list (request
|
|
161
|
+
validation), so a frontend reading `detail` to report an error is never
|
|
162
|
+
evidence of a dropped success-model field.
|
|
163
|
+
|
|
164
|
+
The attribute is **absent** whenever the wire names are not statically
|
|
165
|
+
computable, never partial:
|
|
166
|
+
|
|
167
|
+
- a model configuring alias generation (`model_config = ConfigDict(...)`
|
|
168
|
+
or a pydantic v1 `class Config`) — every name differs;
|
|
169
|
+
- a base class outside the scanned set — inherited fields unknown;
|
|
170
|
+
- a shape that is not one model: `dict`, a union of two models, a
|
|
171
|
+
computed annotation, no annotation at all.
|
|
172
|
+
|
|
173
|
+
The existing `responseModel` attribute keeps its exact previous meaning
|
|
174
|
+
(the decorator declaration only) and its exact previous bytes; a return
|
|
175
|
+
annotation is a new fact, never a change of the old one.
|
|
176
|
+
|
|
177
|
+
`gateforge check` cross-checks these names against the fields a frontend
|
|
178
|
+
actually reads (reported by `@gate-forge/pack-http` as `responseReads`)
|
|
179
|
+
and emits one **non-blocking** `RESPONSE_FIELD_MISSING_FROM_MODEL`
|
|
180
|
+
advisory per field a joined endpoint's model does not declare — naming the
|
|
181
|
+
endpoint, the field, the frontend file and line, and the names the model
|
|
182
|
+
does declare. A field counts as present under its own name, under the other
|
|
183
|
+
case style (`due_date` ≡ `dueDate`) or under a declared alias. A
|
|
184
|
+
repository whose routes declare no provable response model produces no
|
|
185
|
+
advisory and a byte-identical report.
|
|
186
|
+
|
|
148
187
|
## Setup
|
|
149
188
|
|
|
150
189
|
```yaml
|
package/dist/detector.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AA4CA,2EAA2E;AAC3E,eAAO,MAAM,eAAe,UAAkD,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,4BAA4B,CAAC;AAElE,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,2BAA2B,EAAE,iBAAsB,CAAC;AAsBjE;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,CAkClF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AA4CA,2EAA2E;AAC3E,eAAO,MAAM,eAAe,UAAkD,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,4BAA4B,CAAC;AAElE,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,2BAA2B,EAAE,iBAAsB,CAAC;AAsBjE;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,CAkClF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAAC,UAAU,CAOlF;AAED,iDAAiD;AACjD,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CACN,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC;QACT,SAAS,EAAE,OAAO,EAAE,CAAC;QACrB,UAAU,EAAE,OAAO,EAAE,CAAC;QACtB,QAAQ,EAAE,OAAO,EAAE,CAAC;QACpB,qBAAqB,EAAE,OAAO,EAAE,CAAC;QACjC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC,CAAC;CACJ;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,eAAe,CAuC3F"}
|
package/dist/detector.js
CHANGED
|
@@ -107,7 +107,11 @@ export function readFastapiScanConfigOrNull(path) {
|
|
|
107
107
|
*/
|
|
108
108
|
export function pythonEnvironment(extra = []) {
|
|
109
109
|
const entries = [...extra, PACK_PYTHON_DIR, PROTOCOL_PYTHON_DIR];
|
|
110
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
...process.env,
|
|
112
|
+
PYTHONDONTWRITEBYTECODE: '1',
|
|
113
|
+
PYTHONPATH: entries.join(delimiter),
|
|
114
|
+
};
|
|
111
115
|
}
|
|
112
116
|
/**
|
|
113
117
|
* Creates a discover-capable detector module. The default export of the
|
package/dist/detector.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"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"}
|
package/dist/facts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../src/facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,qEAAqE;AACrE,KAAK,QAAQ,GAAG,YAAY,CAAC;AAE7B,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,gEAAgE;IAChE,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,0EAA0E;IAC1E,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;IACxE,kEAAkE;IAClE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;CAC/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,uBAAuB,
|
|
1
|
+
{"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../src/facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,qEAAqE;AACrE,KAAK,QAAQ,GAAG,YAAY,CAAC;AAE7B,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,gEAAgE;IAChE,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,0EAA0E;IAC1E,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;IACxE,kEAAkE;IAClE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;CAC/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,uBAAuB,CAqFzB"}
|
package/dist/facts.js
CHANGED
|
@@ -81,6 +81,16 @@ export function canonicalizeFacts(resources) {
|
|
|
81
81
|
if (typeof responseModel === 'string' && responseModel.length > 0) {
|
|
82
82
|
fact.responseSchemaSymbols = [responseModel];
|
|
83
83
|
}
|
|
84
|
+
// Wire names the response model answers to (plan 2026-09-25 Phase 4b
|
|
85
|
+
// item 5). The python scanner emits them only for a provably
|
|
86
|
+
// concrete model, so anything else arrives absent here too and the
|
|
87
|
+
// shared fact carries no field list to compare frontend reads against.
|
|
88
|
+
const responseModelFields = attributes['responseModelFields'];
|
|
89
|
+
if (Array.isArray(responseModelFields) && responseModelFields.length > 0) {
|
|
90
|
+
const names = responseModelFields.filter((name) => typeof name === 'string' && name.length > 0);
|
|
91
|
+
if (names.length === responseModelFields.length)
|
|
92
|
+
fact.responseModelFields = names;
|
|
93
|
+
}
|
|
84
94
|
const requestSchemas = attributes['requestSchemaSymbols'];
|
|
85
95
|
if (Array.isArray(requestSchemas)) {
|
|
86
96
|
const names = requestSchemas.filter((name) => typeof name === 'string' && name.length > 0);
|
package/dist/facts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facts.js","sourceRoot":"","sources":["../src/facts.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"facts.js","sourceRoot":"","sources":["../src/facts.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gate-forge/pack-fastapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "FastAPI server-route detector: Python AST scanner served over GPP/3 (pack-sqlalchemy pattern), emitting http.contract evidence facts with canonical effective paths plus exposure/lifecycle signals (ADR 0004 D1, plan phase 2).",
|
|
6
6
|
"type": "module",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"test": "vitest run"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@gate-forge/core": "^0.
|
|
31
|
-
"@gate-forge/http-contract": "^0.
|
|
32
|
-
"@gate-forge/plugin-protocol": "^0.
|
|
30
|
+
"@gate-forge/core": "^0.8.0",
|
|
31
|
+
"@gate-forge/http-contract": "^0.8.0",
|
|
32
|
+
"@gate-forge/plugin-protocol": "^0.8.0",
|
|
33
33
|
"zod": "^4.1.5"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -128,6 +128,11 @@ class RouteDef:
|
|
|
128
128
|
handler: str # function name
|
|
129
129
|
is_async: bool
|
|
130
130
|
response_model: str | None
|
|
131
|
+
# The model FastAPI actually answers with: the decorator's explicit
|
|
132
|
+
# `response_model=` when present, else the handler's return annotation
|
|
133
|
+
# (FastAPI's own default). Additive: `response_model` above keeps its
|
|
134
|
+
# exact previous meaning and emission.
|
|
135
|
+
effective_response_model: str | None
|
|
131
136
|
request_schemas: list[str]
|
|
132
137
|
tags: list[str]
|
|
133
138
|
operation_id: str | None
|
|
@@ -222,6 +227,32 @@ class ImportRef:
|
|
|
222
227
|
level: int # relative-import depth (0: absolute)
|
|
223
228
|
|
|
224
229
|
|
|
230
|
+
@dataclass
|
|
231
|
+
class ModelDef:
|
|
232
|
+
"""One class definition whose response fields are statically provable.
|
|
233
|
+
|
|
234
|
+
Plan 2026-09-25 Phase 4b item 5: a dropped response-model field is
|
|
235
|
+
invisible from the frontend (every test mocks it), so the pack proves
|
|
236
|
+
the wire names a model answers to. ``proven`` is the fail-closed
|
|
237
|
+
switch: a model that configures alias generation (pydantic v1
|
|
238
|
+
``class Config`` / v2 ``model_config``) has wire names this AST pass
|
|
239
|
+
cannot compute, so it is reported as NOT proven and contributes no
|
|
240
|
+
field list at all — silence, never a wrong answer.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
name: str
|
|
244
|
+
fields: list[str] = field(default_factory=list) # declared names, written order
|
|
245
|
+
aliases: dict[str, str] = field(default_factory=dict) # field name -> declared alias
|
|
246
|
+
bases: list[str] = field(default_factory=list) # base class names
|
|
247
|
+
proven: bool = True
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Base classes that carry no response fields of their own. Any OTHER base
|
|
251
|
+
# must resolve inside the scanned set: an unresolvable base means inherited
|
|
252
|
+
# fields are unknown, so the model is not proven.
|
|
253
|
+
_FIELDLESS_BASES = frozenset({"BaseModel", "BaseModelRoot", "RootModel", "object"})
|
|
254
|
+
|
|
255
|
+
|
|
225
256
|
@dataclass
|
|
226
257
|
class FileIndex:
|
|
227
258
|
"""Everything one parsed file contributes to the mount graph."""
|
|
@@ -234,6 +265,7 @@ class FileIndex:
|
|
|
234
265
|
functions: dict[str, FunctionIncludes] = field(default_factory=dict)
|
|
235
266
|
helper_calls: list[HelperCall] = field(default_factory=list)
|
|
236
267
|
unsupported: list[tuple[ast.AST, list[str]]] = field(default_factory=list)
|
|
268
|
+
models: dict[str, ModelDef] = field(default_factory=dict)
|
|
237
269
|
|
|
238
270
|
|
|
239
271
|
def loc(relpath: str, node: ast.AST) -> dict:
|
|
@@ -258,6 +290,42 @@ def _dotted_name(node: ast.AST | None) -> str | None:
|
|
|
258
290
|
return None
|
|
259
291
|
|
|
260
292
|
|
|
293
|
+
# Containers whose single element type IS the response model
|
|
294
|
+
# (``-> list[InvoiceOut]``); anything else (``dict[str, X]``, a union of
|
|
295
|
+
# two models, a computed expression) is an unknown shape.
|
|
296
|
+
_MODEL_CONTAINERS = frozenset({
|
|
297
|
+
"list", "List", "Sequence", "Iterable", "set", "Set", "frozenset",
|
|
298
|
+
"Optional", "Union", "tuple", "Tuple",
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _model_name(node: ast.AST | None) -> str | None:
|
|
303
|
+
"""The model class a response annotation or ``response_model=`` names.
|
|
304
|
+
|
|
305
|
+
Unwraps the containers FastAPI itself unwraps (``list[X]``,
|
|
306
|
+
``Optional[X]``, ``X | None``) and returns the dotted class name. Any
|
|
307
|
+
other shape — a dict, a union of different models, a computed
|
|
308
|
+
expression — returns None: an unknown shape has no field list, and the
|
|
309
|
+
advisory must never guess one.
|
|
310
|
+
"""
|
|
311
|
+
if node is None:
|
|
312
|
+
return None
|
|
313
|
+
if isinstance(node, (ast.Name, ast.Attribute)):
|
|
314
|
+
return _dotted_name(node)
|
|
315
|
+
if isinstance(node, ast.Subscript):
|
|
316
|
+
base = _dotted_name(node.value)
|
|
317
|
+
if base is None or base.split(".")[-1] not in _MODEL_CONTAINERS:
|
|
318
|
+
return None
|
|
319
|
+
elements = _elements(node.slice) or [node.slice]
|
|
320
|
+
names = {_model_name(element) for element in elements}
|
|
321
|
+
names.discard(None)
|
|
322
|
+
return names.pop() if len(names) == 1 else None
|
|
323
|
+
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
|
|
324
|
+
left, right = _model_name(node.left), _model_name(node.right)
|
|
325
|
+
return left if left is not None and left == right else None
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
|
|
261
329
|
def _keyword(call: ast.Call, name: str) -> ast.AST | None:
|
|
262
330
|
for keyword in call.keywords:
|
|
263
331
|
if keyword.arg == name:
|
|
@@ -271,6 +339,61 @@ def _elements(node: ast.AST | None) -> list[ast.AST] | None:
|
|
|
271
339
|
return None
|
|
272
340
|
|
|
273
341
|
|
|
342
|
+
def _declared_alias(value: ast.AST | None) -> str | None:
|
|
343
|
+
"""The static alias a ``Field(alias=...)`` declaration carries."""
|
|
344
|
+
if not isinstance(value, ast.Call):
|
|
345
|
+
return None
|
|
346
|
+
for keyword in value.keywords:
|
|
347
|
+
if keyword.arg in {"alias", "validation_alias", "serialization_alias"}:
|
|
348
|
+
return _static_string(keyword.value)
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _is_class_var(annotation: ast.AST | None) -> bool:
|
|
353
|
+
"""``ClassVar[...]`` annotations are not response fields."""
|
|
354
|
+
if not isinstance(annotation, ast.Subscript):
|
|
355
|
+
return False
|
|
356
|
+
base = _dotted_name(annotation.value)
|
|
357
|
+
return base is not None and base.split(".")[-1] == "ClassVar"
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _model_def(node: ast.ClassDef) -> ModelDef:
|
|
361
|
+
"""The statically provable field list of one class definition.
|
|
362
|
+
|
|
363
|
+
Alias generation (``model_config = ConfigDict(alias_generator=...)``
|
|
364
|
+
or a pydantic v1 ``class Config``) makes the wire names
|
|
365
|
+
uncomputable here, so the model is reported as NOT proven rather than
|
|
366
|
+
as a partial list. ``ClassVar`` members and private names are not
|
|
367
|
+
pydantic fields and are skipped.
|
|
368
|
+
"""
|
|
369
|
+
model = ModelDef(name=node.name)
|
|
370
|
+
for base in node.bases:
|
|
371
|
+
name = _dotted_name(base)
|
|
372
|
+
model.bases.append(name if name is not None else "?")
|
|
373
|
+
for statement in node.body:
|
|
374
|
+
if isinstance(statement, ast.AnnAssign) and isinstance(statement.target, ast.Name):
|
|
375
|
+
name = statement.target.id
|
|
376
|
+
if name.startswith("_") or _is_class_var(statement.annotation):
|
|
377
|
+
continue
|
|
378
|
+
if name == "model_config":
|
|
379
|
+
model.proven = False
|
|
380
|
+
continue
|
|
381
|
+
model.fields.append(name)
|
|
382
|
+
alias = _declared_alias(statement.value)
|
|
383
|
+
if alias is not None:
|
|
384
|
+
model.aliases[name] = alias
|
|
385
|
+
elif isinstance(statement, ast.Assign):
|
|
386
|
+
for target in statement.targets:
|
|
387
|
+
if isinstance(target, ast.Name) and not target.id.startswith("_"):
|
|
388
|
+
if target.id == "model_config":
|
|
389
|
+
model.proven = False
|
|
390
|
+
else:
|
|
391
|
+
model.fields.append(target.id)
|
|
392
|
+
elif isinstance(statement, ast.ClassDef) and statement.name in {"Config", "model_config"}:
|
|
393
|
+
model.proven = False
|
|
394
|
+
return model
|
|
395
|
+
|
|
396
|
+
|
|
274
397
|
class _ModuleVisitor(ast.NodeVisitor):
|
|
275
398
|
"""Collects routers, apps, imports, include edges, and route decorators.
|
|
276
399
|
|
|
@@ -321,6 +444,12 @@ class _ModuleVisitor(ast.NodeVisitor):
|
|
|
321
444
|
)
|
|
322
445
|
self.generic_visit(node)
|
|
323
446
|
|
|
447
|
+
# -- response models -----------------------------------------------------
|
|
448
|
+
|
|
449
|
+
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
|
450
|
+
self.index.models[node.name] = _model_def(node)
|
|
451
|
+
self.generic_visit(node)
|
|
452
|
+
|
|
324
453
|
# -- functions: route decorators -----------------------------------------
|
|
325
454
|
|
|
326
455
|
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
@@ -394,7 +523,8 @@ class _ModuleVisitor(ast.NodeVisitor):
|
|
|
394
523
|
if owner_ref is not None:
|
|
395
524
|
owner_ref.routes.append(RouteDef(
|
|
396
525
|
methods=[], path=None, node=node, file=self.index.relpath, handler=fn.name,
|
|
397
|
-
is_async=is_async, response_model=None,
|
|
526
|
+
is_async=is_async, response_model=None,
|
|
527
|
+
effective_response_model=None, request_schemas=[],
|
|
398
528
|
tags=[], operation_id=None,
|
|
399
529
|
))
|
|
400
530
|
return
|
|
@@ -414,6 +544,9 @@ class _ModuleVisitor(ast.NodeVisitor):
|
|
|
414
544
|
handler=fn.name,
|
|
415
545
|
is_async=is_async,
|
|
416
546
|
response_model=_dotted_name(_keyword(node, "response_model")),
|
|
547
|
+
effective_response_model=(
|
|
548
|
+
_model_name(_keyword(node, "response_model")) or _model_name(fn.returns)
|
|
549
|
+
),
|
|
417
550
|
request_schemas=_request_schema_names(fn, path),
|
|
418
551
|
tags=[
|
|
419
552
|
s for s in (
|
|
@@ -656,6 +789,94 @@ def _resolve_import(
|
|
|
656
789
|
return ImportResolution(module=None)
|
|
657
790
|
|
|
658
791
|
|
|
792
|
+
class _ModelIndex:
|
|
793
|
+
"""Resolves a response-model symbol to the wire names it answers to.
|
|
794
|
+
|
|
795
|
+
Bounded like every other channel here: the defining class must be in
|
|
796
|
+
the scanned set, reachable as a same-file definition or through one
|
|
797
|
+
resolved import, and every base class must resolve too. Anything the
|
|
798
|
+
AST pass cannot compute (an alias generator, an unresolvable base, an
|
|
799
|
+
unknown shape) yields ``None`` — the caller then reports no fields at
|
|
800
|
+
all rather than a partial list.
|
|
801
|
+
"""
|
|
802
|
+
|
|
803
|
+
def __init__(self, indexes: dict[str, FileIndex], import_roots: tuple[str, ...] = ()) -> None:
|
|
804
|
+
self.models: dict[str, dict[str, ModelDef]] = {
|
|
805
|
+
relpath: index.models for relpath, index in indexes.items()
|
|
806
|
+
}
|
|
807
|
+
self.indexes = indexes
|
|
808
|
+
self.import_roots = import_roots
|
|
809
|
+
self.scanned: frozenset[str] = frozenset(indexes)
|
|
810
|
+
self.module_map = {_module_of(rel): rel for rel in indexes}
|
|
811
|
+
|
|
812
|
+
def fields_for(self, relpath: str, model: str | None) -> list[str] | None:
|
|
813
|
+
"""Every wire name one response model answers to, or None."""
|
|
814
|
+
if model is None or model == "":
|
|
815
|
+
return None
|
|
816
|
+
name = model
|
|
817
|
+
prefix = ""
|
|
818
|
+
if "." in model:
|
|
819
|
+
prefix, name = model.rsplit(".", 1)
|
|
820
|
+
if prefix not in self.module_map:
|
|
821
|
+
return None
|
|
822
|
+
located = self._locate(relpath, name)
|
|
823
|
+
if located is None:
|
|
824
|
+
return None
|
|
825
|
+
found = self._collect(relpath, name, set())
|
|
826
|
+
if found is None or not found:
|
|
827
|
+
return None
|
|
828
|
+
return found
|
|
829
|
+
|
|
830
|
+
def _locate(self, relpath: str, name: str) -> ModelDef | None:
|
|
831
|
+
"""The class definition a name refers to, or None."""
|
|
832
|
+
own = self.models.get(relpath, {}).get(name)
|
|
833
|
+
if own is not None:
|
|
834
|
+
return own
|
|
835
|
+
ref = self.indexes.get(relpath)
|
|
836
|
+
if ref is None:
|
|
837
|
+
return None
|
|
838
|
+
imported = ref.imports.get(name)
|
|
839
|
+
if imported is None or imported.name is None:
|
|
840
|
+
return None
|
|
841
|
+
resolution = _resolve_import(
|
|
842
|
+
relpath,
|
|
843
|
+
(imported.module, imported.level, imported.name),
|
|
844
|
+
self.module_map,
|
|
845
|
+
self.import_roots,
|
|
846
|
+
self.scanned,
|
|
847
|
+
)
|
|
848
|
+
if resolution.module is None:
|
|
849
|
+
return None
|
|
850
|
+
target = self.module_map.get(resolution.module)
|
|
851
|
+
return None if target is None else self.models.get(target, {}).get(name)
|
|
852
|
+
|
|
853
|
+
def _collect(self, relpath: str, name: str, seen: set[tuple[str, str]]) -> list[str] | None:
|
|
854
|
+
"""Field and alias names of one model plus its provable bases."""
|
|
855
|
+
key = (relpath, name)
|
|
856
|
+
if key in seen:
|
|
857
|
+
return None
|
|
858
|
+
seen.add(key)
|
|
859
|
+
located = self._locate(relpath, name)
|
|
860
|
+
if located is None or not located.proven:
|
|
861
|
+
return None
|
|
862
|
+
names: list[str] = []
|
|
863
|
+
for base in located.bases:
|
|
864
|
+
if base in _FIELDLESS_BASES:
|
|
865
|
+
continue
|
|
866
|
+
if base == "?":
|
|
867
|
+
return None
|
|
868
|
+
inherited = self._collect(relpath, base, seen)
|
|
869
|
+
if inherited is None:
|
|
870
|
+
return None
|
|
871
|
+
names.extend(inherited)
|
|
872
|
+
for field in located.fields:
|
|
873
|
+
alias = located.aliases.get(field)
|
|
874
|
+
for value in (alias, field) if alias is not None else (field,):
|
|
875
|
+
if value not in names:
|
|
876
|
+
names.append(value)
|
|
877
|
+
return names
|
|
878
|
+
|
|
879
|
+
|
|
659
880
|
class _Resolver:
|
|
660
881
|
"""Composes effective mounted paths over the cross-file mount graph."""
|
|
661
882
|
|
|
@@ -664,6 +885,7 @@ class _Resolver:
|
|
|
664
885
|
self.import_roots = import_roots
|
|
665
886
|
self.scanned: frozenset[str] = frozenset(indexes)
|
|
666
887
|
self.module_map = {_module_of(rel): rel for rel in indexes}
|
|
888
|
+
self.models = _ModelIndex(indexes, import_roots)
|
|
667
889
|
self.facts: list[dict] = []
|
|
668
890
|
self.unresolved: list[dict] = []
|
|
669
891
|
|
|
@@ -1257,33 +1479,56 @@ class _Resolver:
|
|
|
1257
1479
|
continue
|
|
1258
1480
|
for method in route.methods:
|
|
1259
1481
|
self.facts.append(
|
|
1260
|
-
_fact(route.file, route, method, prefix + route.path, mount)
|
|
1482
|
+
_fact(route.file, route, method, prefix + route.path, mount, self.models)
|
|
1261
1483
|
)
|
|
1262
1484
|
|
|
1263
1485
|
|
|
1264
|
-
def _fact(
|
|
1486
|
+
def _fact(
|
|
1487
|
+
relpath: str,
|
|
1488
|
+
route: RouteDef,
|
|
1489
|
+
method: str,
|
|
1490
|
+
effective_path: str,
|
|
1491
|
+
mount: str,
|
|
1492
|
+
models: _ModelIndex,
|
|
1493
|
+
) -> dict:
|
|
1265
1494
|
handler_qname = f"{relpath[:-3].replace('/', '.')}:{route.handler}"
|
|
1495
|
+
attributes = {
|
|
1496
|
+
"role": "server-route",
|
|
1497
|
+
"method": method,
|
|
1498
|
+
"rawPath": route.path or "",
|
|
1499
|
+
"normalizedPath": "", # canonicalized by the TS wrapper (single impl)
|
|
1500
|
+
"effectivePath": effective_path,
|
|
1501
|
+
"framework": FRAMEWORK,
|
|
1502
|
+
"handlerSymbol": handler_qname,
|
|
1503
|
+
"isAsync": route.is_async,
|
|
1504
|
+
"responseModel": route.response_model,
|
|
1505
|
+
"requestSchemaSymbols": route.request_schemas,
|
|
1506
|
+
"tags": route.tags,
|
|
1507
|
+
"operationId": route.operation_id,
|
|
1508
|
+
"mountProvenance": mount,
|
|
1509
|
+
}
|
|
1510
|
+
# Wire names the response model answers to (plan 2026-09-25 Phase 4b
|
|
1511
|
+
# item 5). Absent whenever the model is not statically provable — an
|
|
1512
|
+
# unresolvable symbol, an alias generator, an unprovable base, a
|
|
1513
|
+
# non-model shape — so the frontend-read check has nothing to compare
|
|
1514
|
+
# against and stays silent.
|
|
1515
|
+
fields = models.fields_for(route.file, route.effective_response_model)
|
|
1516
|
+
if fields is not None:
|
|
1517
|
+
# FastAPI answers a FAILED request with ``{"detail": ...}``
|
|
1518
|
+
# (HTTPException) or a ``detail`` list (request validation), and a
|
|
1519
|
+
# frontend reads it to report the error. It is therefore a
|
|
1520
|
+
# legitimate field of every route this pack reports, never
|
|
1521
|
+
# evidence of a dropped success-model field.
|
|
1522
|
+
if "detail" not in fields:
|
|
1523
|
+
fields.append("detail")
|
|
1524
|
+
attributes["responseModelFields"] = fields
|
|
1266
1525
|
return {
|
|
1267
1526
|
"schemaVersion": 1,
|
|
1268
1527
|
"kind": CONTRACT_KIND,
|
|
1269
1528
|
"source": relpath,
|
|
1270
1529
|
"location": loc(relpath, route.node),
|
|
1271
1530
|
"detectorVersion": VERSION,
|
|
1272
|
-
"attributes":
|
|
1273
|
-
"role": "server-route",
|
|
1274
|
-
"method": method,
|
|
1275
|
-
"rawPath": route.path or "",
|
|
1276
|
-
"normalizedPath": "", # canonicalized by the TS wrapper (single impl)
|
|
1277
|
-
"effectivePath": effective_path,
|
|
1278
|
-
"framework": FRAMEWORK,
|
|
1279
|
-
"handlerSymbol": handler_qname,
|
|
1280
|
-
"isAsync": route.is_async,
|
|
1281
|
-
"responseModel": route.response_model,
|
|
1282
|
-
"requestSchemaSymbols": route.request_schemas,
|
|
1283
|
-
"tags": route.tags,
|
|
1284
|
-
"operationId": route.operation_id,
|
|
1285
|
-
"mountProvenance": mount,
|
|
1286
|
-
},
|
|
1531
|
+
"attributes": attributes,
|
|
1287
1532
|
"id": f"http.contract:{relpath}:{route.handler}:{method}:{effective_path}",
|
|
1288
1533
|
}
|
|
1289
1534
|
|