@descryy/adapter-python 0.2.0 → 0.3.1
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/LICENSE +8 -0
- package/dist/adapter.d.ts +13 -20
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +102 -116
- package/dist/adapter.js.map +1 -1
- package/dist/client.d.ts +22 -41
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +35 -62
- package/dist/client.js.map +1 -1
- package/dist/extract.d.ts +23 -0
- package/dist/extract.d.ts.map +1 -1
- package/dist/extract.js +372 -31
- package/dist/extract.js.map +1 -1
- package/dist/extract.py +59 -4
- package/dist/graphql.d.ts +57 -123
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +62 -130
- package/dist/graphql.js.map +1 -1
- package/dist/module.d.ts +23 -42
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +26 -47
- package/dist/module.js.map +1 -1
- package/dist/orm.d.ts +31 -46
- package/dist/orm.d.ts.map +1 -1
- package/dist/orm.js +53 -89
- package/dist/orm.js.map +1 -1
- package/dist/pydantic.d.ts +76 -91
- package/dist/pydantic.d.ts.map +1 -1
- package/dist/pydantic.js +106 -99
- package/dist/pydantic.js.map +1 -1
- package/dist/routes.d.ts +54 -93
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +78 -135
- package/dist/routes.js.map +1 -1
- package/package.json +15 -6
- package/src/extract.py +59 -4
package/dist/routes.d.ts
CHANGED
|
@@ -1,72 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The HTTP-route framework extractor for Python — FastAPI, Flask and Django.
|
|
3
|
+
* `adapter-openapi` reads a published contract, but most repositories
|
|
4
|
+
* publish none; FastAPI/Flask/Django all declare routes in source (a
|
|
5
|
+
* decorator with method + path template, or a `urlpatterns` list), so
|
|
6
|
+
* reading the declaration is not inference.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* has no route side of the join at all, and most repositories publish nothing.
|
|
9
|
-
* FastAPI, Flask and Django all *declare* their routes in source: a decorator
|
|
10
|
-
* carrying a method and a path template, or a `urlpatterns` list. Reading a
|
|
11
|
-
* declaration is not inference.
|
|
12
|
-
*
|
|
13
|
-
* ## The rule that does all the precision work
|
|
14
|
-
*
|
|
15
|
-
* **A route is admitted only when its receiver resolves to a router or
|
|
16
|
-
* application constructed from a web framework's own module.** Never because an
|
|
17
|
-
* attribute happens to be spelled like an HTTP verb.
|
|
18
|
-
*
|
|
19
|
-
* That is not a theoretical guard. Matching `@x.get`/`@x.post`/`@x.patch` by
|
|
20
|
-
* name alone, measured before this file was written:
|
|
8
|
+
* The precision rule: a route is admitted only when its receiver resolves
|
|
9
|
+
* to a router/application constructed from a web framework's own module,
|
|
10
|
+
* never because an attribute is spelled like an HTTP verb. Measured before
|
|
11
|
+
* this file existed, name-only matching of `@x.get`/`@x.post`/`@x.patch`:
|
|
21
12
|
*
|
|
22
13
|
* | repository | verb-named decorators | actually routes |
|
|
23
14
|
* | --- | --- | --- |
|
|
24
|
-
* | django | 171 `@mock.patch` |
|
|
25
|
-
* | saleor | 1,150 `@mock.patch` |
|
|
26
|
-
* | dispatch | 1 `@app.options`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* api_router.include_router(auth_router, prefix=...) # api.py, aliased
|
|
43
|
-
* @auth_router.get("/me") # auth/views.py
|
|
44
|
-
*
|
|
45
|
-
* **Every hop of that chain must be followed or the answer is wrong in a way
|
|
46
|
-
* that reads as right.** Dropping the outermost hop yields `/{organization}/…`
|
|
47
|
-
* instead of `/api/v1/{organization}/…`: a plausible path, no error, no ledger
|
|
48
|
-
* row — and a caller that resolved its base URL to `/api/v1` (DEC-052 followed
|
|
49
|
-
* exactly that, three symbol hops on the consumer side) mints a different
|
|
50
|
-
* endpoint id and joins nothing. That is DEC-055's failure mode: everything
|
|
51
|
-
* normalises, everything resolves, and the join count is silently zero.
|
|
52
|
-
*
|
|
53
|
-
* ## Where the line is
|
|
54
|
-
*
|
|
55
|
-
* A prefix or path written as anything but a literal is **disclosed, never
|
|
56
|
-
* guessed**. Emitting a route for a path built at runtime attaches a caller to
|
|
57
|
-
* an endpoint that does not exist, and unlike a missing edge nothing downstream
|
|
58
|
-
* can tell that from a correct one.
|
|
59
|
-
*
|
|
60
|
-
* The split with `extract.py` is this adapter's standing one: the Python side
|
|
61
|
-
* records syntactic form and names no framework, and every decision about what
|
|
62
|
-
* a form *means* is taken here.
|
|
15
|
+
* | django | 171 `@mock.patch` | 0 |
|
|
16
|
+
* | saleor | 1,150 `@mock.patch` | 0 |
|
|
17
|
+
* | dispatch | 1 `@app.options` (Slack Bolt) | 0 |
|
|
18
|
+
*
|
|
19
|
+
* Golden pattern 12's lesson: a node type comes from provenance or nothing.
|
|
20
|
+
*
|
|
21
|
+
* Everything below the receiver check is arithmetic on written strings: a
|
|
22
|
+
* served path is the mount prefix chain plus the decorator's path, and
|
|
23
|
+
* every hop must be followed — dropping one yields a plausible wrong path
|
|
24
|
+
* with no error and no ledger row, and a caller resolving its own base URL
|
|
25
|
+
* (DEC-052) would mint a different endpoint id and silently join nothing
|
|
26
|
+
* (DEC-055's failure mode). A prefix or path written as anything but a
|
|
27
|
+
* literal is disclosed, never guessed, since a runtime-built path attaches
|
|
28
|
+
* a caller to an endpoint that doesn't exist and nothing downstream can
|
|
29
|
+
* tell that from a correct edge.
|
|
30
|
+
*
|
|
31
|
+
* `extract.py` records syntactic form and names no framework; every
|
|
32
|
+
* "what does this form mean" decision is taken here.
|
|
63
33
|
*/
|
|
64
34
|
import type { PyCall } from "./extract.ts";
|
|
65
35
|
export type RouteFramework = "fastapi" | "flask" | "django";
|
|
66
36
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
37
|
+
* Constructors that make a receiver a router, and the modules they must come
|
|
38
|
+
* from — a local class called `Blueprint` is not Flask's, and a bare-name
|
|
39
|
+
* match would admit it.
|
|
70
40
|
*/
|
|
71
41
|
export declare const ROUTER_CONSTRUCTORS: Readonly<Record<RouteFramework, Readonly<Record<string, ReadonlySet<string>>>>>;
|
|
72
42
|
/** `django.urls.path`/`re_path`/`url`, and `include` for a nested urlconf. */
|
|
@@ -92,12 +62,9 @@ export interface RouterDecl {
|
|
|
92
62
|
readonly line: number;
|
|
93
63
|
}
|
|
94
64
|
/**
|
|
95
|
-
* Is this the application itself
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* non-event. `FastAPI()` and `Flask()` are roots and serve their routes at the
|
|
99
|
-
* paths written on them. An `APIRouter` or a `Blueprint` that nothing mounts
|
|
100
|
-
* serves nothing at all, and the path it would serve is genuinely unknowable.
|
|
65
|
+
* Is this the application itself, not a router that must be mounted? Decides
|
|
66
|
+
* whether "nothing mounts this" is a disclosure or a non-event — `FastAPI()`/
|
|
67
|
+
* `Flask()` are roots; an unmounted `APIRouter`/`Blueprint` serves nothing.
|
|
101
68
|
*/
|
|
102
69
|
export declare function isApplicationRouter(router: RouterDecl): boolean;
|
|
103
70
|
/** One router mounted inside another. */
|
|
@@ -134,19 +101,16 @@ export declare function frameworkOfConstructor(callee: string, moduleOfImport: (
|
|
|
134
101
|
} | null;
|
|
135
102
|
/**
|
|
136
103
|
* Routers declared in one file, by the local name that holds them.
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* only thing separating Flask's `Blueprint` from a class of the same name.
|
|
104
|
+
* `moduleOfImport` is the only thing separating Flask's `Blueprint` from a
|
|
105
|
+
* class of the same name.
|
|
140
106
|
*/
|
|
141
107
|
export declare function routersIn(file: string, calls: readonly PyCall[], assignedLocal: (call: PyCall) => string | undefined, moduleOfImport: (name: string) => string | undefined): RouterDecl[];
|
|
142
108
|
/** Mounts declared in one file. */
|
|
143
109
|
export declare function mountsIn(file: string, calls: readonly PyCall[]): RouterMount[];
|
|
144
110
|
/**
|
|
145
|
-
* Route declarations from one decorator call
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* *not* checked here — the caller does that against the resolved router table,
|
|
149
|
-
* because whether `app` is a Flask application or a Slack Bolt one cannot be
|
|
111
|
+
* Route declarations from one decorator call, or `null` when it's not a
|
|
112
|
+
* route. The receiver is not checked here — the caller does that against the
|
|
113
|
+
* resolved router table, since whether `app` is Flask or Slack Bolt can't be
|
|
150
114
|
* decided from this file alone.
|
|
151
115
|
*/
|
|
152
116
|
export declare function routeFromDecorator(file: string, call: PyCall, handler: readonly string[] | null): RouteDecl | null;
|
|
@@ -167,29 +131,29 @@ export declare function odooRouteFromDecorator(file: string, call: PyCall, handl
|
|
|
167
131
|
export declare const DRF_ROUTER_CONSTRUCTORS: ReadonlySet<string>;
|
|
168
132
|
/**
|
|
169
133
|
* DRF's fixed action table — what `DefaultRouter`/`SimpleRouter` always
|
|
170
|
-
* generate for a registered viewset, unconditionally
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
134
|
+
* generate for a registered viewset, unconditionally (the router's own
|
|
135
|
+
* documented contract, not an inference; an unimplemented action still
|
|
136
|
+
* gets the URL and 405s at request time).
|
|
137
|
+
*
|
|
138
|
+
* `action` is the viewset method name DRF's own dispatch calls for it — the
|
|
139
|
+
* same name a subclass would override. Most `ModelViewSet` registrations
|
|
140
|
+
* never write these in source at all (they come from DRF's own installed
|
|
141
|
+
* mixins), so resolving `action` to a same-file declaration is expected to
|
|
142
|
+
* miss far more often than it hits; a viewset that *does* override one is
|
|
143
|
+
* exactly the case worth naming.
|
|
176
144
|
*/
|
|
177
145
|
export declare const DRF_ACTIONS: readonly {
|
|
178
146
|
readonly method: string;
|
|
179
147
|
readonly suffix: string;
|
|
148
|
+
readonly action: string;
|
|
180
149
|
}[];
|
|
181
150
|
export declare function djangoUrlFunctionOf(callee: string, moduleOfImport: (name: string) => string | undefined): string | null;
|
|
182
151
|
/**
|
|
183
152
|
* `re_path`/`url` write a param as `(?P<name>...)`, not `<type:name>`.
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* which every Django regex route carries and which are not part of the
|
|
189
|
-
* served path), and a named group, which names its own placeholder and
|
|
190
|
-
* needs no interpretation. Anything else regex-shaped left afterwards —
|
|
191
|
-
* an unnamed group, a character class, a quantifier on a literal segment —
|
|
192
|
-
* means the pattern is refused rather than approximated.
|
|
153
|
+
* Translating an arbitrary regex into a template isn't attempted — the same
|
|
154
|
+
* guess `client.ts`'s `templateOfSource` refuses. Reads only the anchors
|
|
155
|
+
* (`^`/`$`, not part of the served path) and named groups; anything else
|
|
156
|
+
* regex-shaped left afterwards means the pattern is refused, not approximated.
|
|
193
157
|
*/
|
|
194
158
|
export declare function djangoRegexPathOf(raw: string): string | null;
|
|
195
159
|
/** One `path()`/`re_path()`/`url()` call, read but not yet chain-resolved. */
|
|
@@ -239,11 +203,8 @@ export declare function drfRouterLocalsIn(calls: readonly PyCall[], localOfCall:
|
|
|
239
203
|
/** Every `.register(...)` call on a receiver, whether or not it is a DRF router — the caller filters. */
|
|
240
204
|
export declare function drfRegisterCallsIn(file: string, calls: readonly PyCall[]): DrfRegisterEntry[];
|
|
241
205
|
/**
|
|
242
|
-
* Join a mount prefix to a route path.
|
|
243
|
-
*
|
|
244
|
-
* Frameworks are lenient about the slash between the two and repositories write
|
|
245
|
-
* both forms; the served path is the same either way, and two spellings of one
|
|
246
|
-
* path must not become two endpoints.
|
|
206
|
+
* Join a mount prefix to a route path. Frameworks are lenient about the
|
|
207
|
+
* slash between the two, and two spellings of one path must not become two endpoints.
|
|
247
208
|
*/
|
|
248
209
|
export declare function joinPath(prefix: string, path: string): string;
|
|
249
210
|
//# sourceMappingURL=routes.d.ts.map
|
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/routes.js
CHANGED
|
@@ -1,70 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The HTTP-route framework extractor for Python — FastAPI, Flask and Django.
|
|
3
|
+
* `adapter-openapi` reads a published contract, but most repositories
|
|
4
|
+
* publish none; FastAPI/Flask/Django all declare routes in source (a
|
|
5
|
+
* decorator with method + path template, or a `urlpatterns` list), so
|
|
6
|
+
* reading the declaration is not inference.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* has no route side of the join at all, and most repositories publish nothing.
|
|
9
|
-
* FastAPI, Flask and Django all *declare* their routes in source: a decorator
|
|
10
|
-
* carrying a method and a path template, or a `urlpatterns` list. Reading a
|
|
11
|
-
* declaration is not inference.
|
|
12
|
-
*
|
|
13
|
-
* ## The rule that does all the precision work
|
|
14
|
-
*
|
|
15
|
-
* **A route is admitted only when its receiver resolves to a router or
|
|
16
|
-
* application constructed from a web framework's own module.** Never because an
|
|
17
|
-
* attribute happens to be spelled like an HTTP verb.
|
|
18
|
-
*
|
|
19
|
-
* That is not a theoretical guard. Matching `@x.get`/`@x.post`/`@x.patch` by
|
|
20
|
-
* name alone, measured before this file was written:
|
|
8
|
+
* The precision rule: a route is admitted only when its receiver resolves
|
|
9
|
+
* to a router/application constructed from a web framework's own module,
|
|
10
|
+
* never because an attribute is spelled like an HTTP verb. Measured before
|
|
11
|
+
* this file existed, name-only matching of `@x.get`/`@x.post`/`@x.patch`:
|
|
21
12
|
*
|
|
22
13
|
* | repository | verb-named decorators | actually routes |
|
|
23
14
|
* | --- | --- | --- |
|
|
24
|
-
* | django | 171 `@mock.patch` |
|
|
25
|
-
* | saleor | 1,150 `@mock.patch` |
|
|
26
|
-
* | dispatch | 1 `@app.options`
|
|
27
|
-
*
|
|
28
|
-
* `unittest.mock.patch` is spelled exactly like the HTTP verb, and on saleor a
|
|
29
|
-
* name rule would have invented 1,150 routes — every one of them a false claim
|
|
30
|
-
* about a URL that does not exist. Slack Bolt's `app.options()` registers an
|
|
31
|
-
* *action id*, not a path. This is golden pattern 12's lesson and §4 rule 3: a
|
|
32
|
-
* node type comes from provenance — an import, a decorator, a base class — or
|
|
33
|
-
* from nothing.
|
|
34
|
-
*
|
|
35
|
-
* ## Everything below the receiver check is arithmetic on written strings
|
|
36
|
-
*
|
|
37
|
-
* A served path is a router's mount prefix chain plus the path at the decorator.
|
|
38
|
-
* On dispatch that chain is four hops and crosses files under import aliases:
|
|
39
|
-
*
|
|
40
|
-
* app.mount("/api/v1", app=api) # main.py
|
|
41
|
-
* api.include_router(api_router) # main.py
|
|
42
|
-
* api_router.include_router(auth_router, prefix=...) # api.py, aliased
|
|
43
|
-
* @auth_router.get("/me") # auth/views.py
|
|
15
|
+
* | django | 171 `@mock.patch` | 0 |
|
|
16
|
+
* | saleor | 1,150 `@mock.patch` | 0 |
|
|
17
|
+
* | dispatch | 1 `@app.options` (Slack Bolt) | 0 |
|
|
44
18
|
*
|
|
45
|
-
*
|
|
46
|
-
* that reads as right.** Dropping the outermost hop yields `/{organization}/…`
|
|
47
|
-
* instead of `/api/v1/{organization}/…`: a plausible path, no error, no ledger
|
|
48
|
-
* row — and a caller that resolved its base URL to `/api/v1` (DEC-052 followed
|
|
49
|
-
* exactly that, three symbol hops on the consumer side) mints a different
|
|
50
|
-
* endpoint id and joins nothing. That is DEC-055's failure mode: everything
|
|
51
|
-
* normalises, everything resolves, and the join count is silently zero.
|
|
19
|
+
* Golden pattern 12's lesson: a node type comes from provenance or nothing.
|
|
52
20
|
*
|
|
53
|
-
*
|
|
21
|
+
* Everything below the receiver check is arithmetic on written strings: a
|
|
22
|
+
* served path is the mount prefix chain plus the decorator's path, and
|
|
23
|
+
* every hop must be followed — dropping one yields a plausible wrong path
|
|
24
|
+
* with no error and no ledger row, and a caller resolving its own base URL
|
|
25
|
+
* (DEC-052) would mint a different endpoint id and silently join nothing
|
|
26
|
+
* (DEC-055's failure mode). A prefix or path written as anything but a
|
|
27
|
+
* literal is disclosed, never guessed, since a runtime-built path attaches
|
|
28
|
+
* a caller to an endpoint that doesn't exist and nothing downstream can
|
|
29
|
+
* tell that from a correct edge.
|
|
54
30
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* an endpoint that does not exist, and unlike a missing edge nothing downstream
|
|
58
|
-
* can tell that from a correct one.
|
|
59
|
-
*
|
|
60
|
-
* The split with `extract.py` is this adapter's standing one: the Python side
|
|
61
|
-
* records syntactic form and names no framework, and every decision about what
|
|
62
|
-
* a form *means* is taken here.
|
|
31
|
+
* `extract.py` records syntactic form and names no framework; every
|
|
32
|
+
* "what does this form mean" decision is taken here.
|
|
63
33
|
*/
|
|
64
34
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
35
|
+
* Constructors that make a receiver a router, and the modules they must come
|
|
36
|
+
* from — a local class called `Blueprint` is not Flask's, and a bare-name
|
|
37
|
+
* match would admit it.
|
|
68
38
|
*/
|
|
69
39
|
export const ROUTER_CONSTRUCTORS = {
|
|
70
40
|
fastapi: {
|
|
@@ -111,12 +81,9 @@ export const MOUNT_ATTRS = new Set([
|
|
|
111
81
|
"mount",
|
|
112
82
|
]);
|
|
113
83
|
/**
|
|
114
|
-
* Is this the application itself
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* non-event. `FastAPI()` and `Flask()` are roots and serve their routes at the
|
|
118
|
-
* paths written on them. An `APIRouter` or a `Blueprint` that nothing mounts
|
|
119
|
-
* serves nothing at all, and the path it would serve is genuinely unknowable.
|
|
84
|
+
* Is this the application itself, not a router that must be mounted? Decides
|
|
85
|
+
* whether "nothing mounts this" is a disclosure or a non-event — `FastAPI()`/
|
|
86
|
+
* `Flask()` are roots; an unmounted `APIRouter`/`Blueprint` serves nothing.
|
|
120
87
|
*/
|
|
121
88
|
export function isApplicationRouter(router) {
|
|
122
89
|
return router.constructor === "FastAPI" || router.constructor === "Flask";
|
|
@@ -129,17 +96,15 @@ export function frameworkOfConstructor(callee, moduleOfImport) {
|
|
|
129
96
|
const modules = table[leaf];
|
|
130
97
|
if (modules === undefined)
|
|
131
98
|
continue;
|
|
132
|
-
//
|
|
133
|
-
// imported
|
|
134
|
-
// checked — this is the whole precision control and it is never skipped.
|
|
99
|
+
// Dotted callee names its module at the call site; bare one was
|
|
100
|
+
// imported. Either way the module is checked — never skipped.
|
|
135
101
|
const written = callee.includes(".") ? callee.slice(0, callee.lastIndexOf(".")) : undefined;
|
|
136
102
|
const imported = moduleOfImport(callee.split(".")[0]);
|
|
137
103
|
const module = written !== undefined && written.includes(".") ? written : imported;
|
|
138
104
|
if (module !== undefined && modules.has(module)) {
|
|
139
105
|
return { framework: framework, constructor: leaf };
|
|
140
106
|
}
|
|
141
|
-
// A bare `APIRouter()` whose import resolved to the
|
|
142
|
-
// rather than to the exact submodule.
|
|
107
|
+
// A bare `APIRouter()` whose import resolved to the package, not the exact submodule.
|
|
143
108
|
if (imported !== undefined && [...modules].some((m) => imported === m || imported.startsWith(`${m}.`))) {
|
|
144
109
|
return { framework: framework, constructor: leaf };
|
|
145
110
|
}
|
|
@@ -159,9 +124,8 @@ function prefixOf(call, keys) {
|
|
|
159
124
|
}
|
|
160
125
|
/**
|
|
161
126
|
* Routers declared in one file, by the local name that holds them.
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* only thing separating Flask's `Blueprint` from a class of the same name.
|
|
127
|
+
* `moduleOfImport` is the only thing separating Flask's `Blueprint` from a
|
|
128
|
+
* class of the same name.
|
|
165
129
|
*/
|
|
166
130
|
export function routersIn(file, calls, assignedLocal, moduleOfImport) {
|
|
167
131
|
const out = [];
|
|
@@ -194,8 +158,8 @@ export function mountsIn(file, calls) {
|
|
|
194
158
|
if (call.receiver === null)
|
|
195
159
|
continue;
|
|
196
160
|
if (call.attribute === "mount") {
|
|
197
|
-
// `app.mount("/api/v1", app=sub)` — Starlette
|
|
198
|
-
//
|
|
161
|
+
// `app.mount("/api/v1", app=sub)` — Starlette: positional prefix, child
|
|
162
|
+
// as keyword or second positional.
|
|
199
163
|
const prefix = typeof call.literals[0] === "string"
|
|
200
164
|
? call.literals[0]
|
|
201
165
|
: call.args.length > 0
|
|
@@ -207,11 +171,9 @@ export function mountsIn(file, calls) {
|
|
|
207
171
|
out.push({ file, scope: call.scope, parentLocal: call.receiver, childLocal: child, prefix, line: call.line });
|
|
208
172
|
continue;
|
|
209
173
|
}
|
|
210
|
-
// `app.register_blueprint(auth.bp)` is as common as the bare-name form
|
|
211
|
-
// was invisible
|
|
212
|
-
//
|
|
213
|
-
// half through its import table; dropping it disclosed real, registered
|
|
214
|
-
// blueprints as never mounted.
|
|
174
|
+
// `app.register_blueprint(auth.bp)` is as common as the bare-name form
|
|
175
|
+
// but was invisible reading only `argNames` (null for an attribute) —
|
|
176
|
+
// dropping it disclosed real, registered blueprints as never mounted.
|
|
215
177
|
const child = call.argNames[0] ??
|
|
216
178
|
call.keywordNames["router"] ??
|
|
217
179
|
call.keywordNames["blueprint"] ??
|
|
@@ -230,11 +192,9 @@ export function mountsIn(file, calls) {
|
|
|
230
192
|
return out;
|
|
231
193
|
}
|
|
232
194
|
/**
|
|
233
|
-
* Route declarations from one decorator call
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* *not* checked here — the caller does that against the resolved router table,
|
|
237
|
-
* because whether `app` is a Flask application or a Slack Bolt one cannot be
|
|
195
|
+
* Route declarations from one decorator call, or `null` when it's not a
|
|
196
|
+
* route. The receiver is not checked here — the caller does that against the
|
|
197
|
+
* resolved router table, since whether `app` is Flask or Slack Bolt can't be
|
|
238
198
|
* decided from this file alone.
|
|
239
199
|
*/
|
|
240
200
|
export function routeFromDecorator(file, call, handler) {
|
|
@@ -250,14 +210,13 @@ export function routeFromDecorator(file, call, handler) {
|
|
|
250
210
|
methods = written.filter((m) => typeof m === "string").map((m) => m.toUpperCase());
|
|
251
211
|
}
|
|
252
212
|
else if (call.opaqueKeywords.includes("methods")) {
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
// mistake `nullable=FLAG` exists to prevent on the ORM side.
|
|
213
|
+
// Verbs are written but unreadable; defaulting to GET would report the
|
|
214
|
+
// opposite of the source as fact.
|
|
256
215
|
return null;
|
|
257
216
|
}
|
|
258
217
|
else {
|
|
259
|
-
// Flask's documented default
|
|
260
|
-
// keyword, so an absent one
|
|
218
|
+
// Flask's documented default; FastAPI's `api_route` requires the
|
|
219
|
+
// keyword, so an absent one means this isn't a route to read.
|
|
261
220
|
methods = ["GET"];
|
|
262
221
|
}
|
|
263
222
|
}
|
|
@@ -282,16 +241,10 @@ export function routeFromDecorator(file, call, handler) {
|
|
|
282
241
|
responseModel: call.keywordText["response_model"] ?? null,
|
|
283
242
|
};
|
|
284
243
|
}
|
|
285
|
-
// ---------------------------------------------------------------------------
|
|
286
244
|
// Odoo — `@http.route(path, methods=[...])`, always on a `Controller`
|
|
287
|
-
// subclass's
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// no mount chain: Odoo controllers write the full absolute path directly in
|
|
291
|
-
// every `@http.route(...)` call, so once the decorator's provenance is
|
|
292
|
-
// verified against `odoo.http` there is nothing left to resolve — the path
|
|
293
|
-
// on the decorator IS the served path.
|
|
294
|
-
// ---------------------------------------------------------------------------
|
|
245
|
+
// subclass's method. Simpler than FastAPI/Flask: no router object, no mount
|
|
246
|
+
// chain — the full absolute path is written directly, so once provenance is
|
|
247
|
+
// verified against `odoo.http` the decorator's path IS the served path.
|
|
295
248
|
const ODOO_HTTP_MODULES = new Set(["odoo.http", "odoo.addons.http"]);
|
|
296
249
|
/** `@http.route(...)` / `@route(...)`, verified against `odoo.http`. */
|
|
297
250
|
export function odooRouteFromDecorator(file, call, handler, moduleOfImport) {
|
|
@@ -319,19 +272,13 @@ export function odooRouteFromDecorator(file, call, handler, moduleOfImport) {
|
|
|
319
272
|
: ["GET"]; // Odoo's own documented default when the keyword is absent
|
|
320
273
|
return { file, scope: call.scope, path: literal, rawPath: raw, methods, line: call.line, handler };
|
|
321
274
|
}
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
// `router
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
// what it was constructed from, and a file's `urlpatterns` list is the
|
|
330
|
-
// router — identified by its own module path, not by a local variable name,
|
|
331
|
-
// because `include("myapp.urls")` names a MODULE, never a local. The prefix
|
|
332
|
-
// chain this earns is walked the same way FastAPI's mount chain is (one
|
|
333
|
-
// `include()` hop at a time), just keyed differently.
|
|
334
|
-
// ---------------------------------------------------------------------------
|
|
275
|
+
// Django's own routing — `urlpatterns`/`path()`/`re_path()`/`url()`, and
|
|
276
|
+
// DRF's `router.register(...)`. Architecturally different from FastAPI/Flask
|
|
277
|
+
// (not a case of `ROUTER_CONSTRUCTORS.django`): no router object, `path()`
|
|
278
|
+
// is a bare function verified by import provenance, and a file's
|
|
279
|
+
// `urlpatterns` list is identified by its module path since
|
|
280
|
+
// `include("myapp.urls")` names a module, never a local. The prefix chain
|
|
281
|
+
// walks the same way as FastAPI's mount chain, just keyed differently.
|
|
335
282
|
/** `path`, `re_path` and the pre-2.0 alias `url` — all three read a route the same way. */
|
|
336
283
|
const DJANGO_URL_FUNCTIONS = new Set(["path", "re_path", "url"]);
|
|
337
284
|
/** `include(...)`, always from `django.urls`. */
|
|
@@ -343,20 +290,24 @@ export const DRF_ROUTER_CONSTRUCTORS = new Set([
|
|
|
343
290
|
]);
|
|
344
291
|
/**
|
|
345
292
|
* DRF's fixed action table — what `DefaultRouter`/`SimpleRouter` always
|
|
346
|
-
* generate for a registered viewset, unconditionally
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
293
|
+
* generate for a registered viewset, unconditionally (the router's own
|
|
294
|
+
* documented contract, not an inference; an unimplemented action still
|
|
295
|
+
* gets the URL and 405s at request time).
|
|
296
|
+
*
|
|
297
|
+
* `action` is the viewset method name DRF's own dispatch calls for it — the
|
|
298
|
+
* same name a subclass would override. Most `ModelViewSet` registrations
|
|
299
|
+
* never write these in source at all (they come from DRF's own installed
|
|
300
|
+
* mixins), so resolving `action` to a same-file declaration is expected to
|
|
301
|
+
* miss far more often than it hits; a viewset that *does* override one is
|
|
302
|
+
* exactly the case worth naming.
|
|
352
303
|
*/
|
|
353
304
|
export const DRF_ACTIONS = [
|
|
354
|
-
{ method: "GET", suffix: "" },
|
|
355
|
-
{ method: "POST", suffix: "" },
|
|
356
|
-
{ method: "GET", suffix: "{param}" },
|
|
357
|
-
{ method: "PUT", suffix: "{param}" },
|
|
358
|
-
{ method: "PATCH", suffix: "{param}" },
|
|
359
|
-
{ method: "DELETE", suffix: "{param}" },
|
|
305
|
+
{ method: "GET", suffix: "", action: "list" },
|
|
306
|
+
{ method: "POST", suffix: "", action: "create" },
|
|
307
|
+
{ method: "GET", suffix: "{param}", action: "retrieve" },
|
|
308
|
+
{ method: "PUT", suffix: "{param}", action: "update" },
|
|
309
|
+
{ method: "PATCH", suffix: "{param}", action: "partial_update" },
|
|
310
|
+
{ method: "DELETE", suffix: "{param}", action: "destroy" },
|
|
360
311
|
];
|
|
361
312
|
/** Which Django URL-module function a callee names, verified by provenance — mirrors `frameworkOfConstructor`. */
|
|
362
313
|
function djangoModuleFunctionOf(callee, names, moduleOfImport) {
|
|
@@ -379,15 +330,10 @@ export function djangoUrlFunctionOf(callee, moduleOfImport) {
|
|
|
379
330
|
}
|
|
380
331
|
/**
|
|
381
332
|
* `re_path`/`url` write a param as `(?P<name>...)`, not `<type:name>`.
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
* which every Django regex route carries and which are not part of the
|
|
387
|
-
* served path), and a named group, which names its own placeholder and
|
|
388
|
-
* needs no interpretation. Anything else regex-shaped left afterwards —
|
|
389
|
-
* an unnamed group, a character class, a quantifier on a literal segment —
|
|
390
|
-
* means the pattern is refused rather than approximated.
|
|
333
|
+
* Translating an arbitrary regex into a template isn't attempted — the same
|
|
334
|
+
* guess `client.ts`'s `templateOfSource` refuses. Reads only the anchors
|
|
335
|
+
* (`^`/`$`, not part of the served path) and named groups; anything else
|
|
336
|
+
* regex-shaped left afterwards means the pattern is refused, not approximated.
|
|
391
337
|
*/
|
|
392
338
|
export function djangoRegexPathOf(raw) {
|
|
393
339
|
let pattern = raw;
|
|
@@ -494,11 +440,8 @@ export function drfRegisterCallsIn(file, calls) {
|
|
|
494
440
|
return out;
|
|
495
441
|
}
|
|
496
442
|
/**
|
|
497
|
-
* Join a mount prefix to a route path.
|
|
498
|
-
*
|
|
499
|
-
* Frameworks are lenient about the slash between the two and repositories write
|
|
500
|
-
* both forms; the served path is the same either way, and two spellings of one
|
|
501
|
-
* path must not become two endpoints.
|
|
443
|
+
* Join a mount prefix to a route path. Frameworks are lenient about the
|
|
444
|
+
* slash between the two, and two spellings of one path must not become two endpoints.
|
|
502
445
|
*/
|
|
503
446
|
export function joinPath(prefix, path) {
|
|
504
447
|
const left = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|