@descryy/adapter-python 0.1.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/dist/adapter.d.ts +75 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +481 -0
- package/dist/adapter.js.map +1 -0
- package/dist/client.d.ts +117 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +347 -0
- package/dist/client.js.map +1 -0
- package/dist/extract.d.ts +231 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +2267 -0
- package/dist/extract.js.map +1 -0
- package/dist/extract.py +1319 -0
- package/dist/graphql.d.ts +197 -0
- package/dist/graphql.d.ts.map +1 -0
- package/dist/graphql.js +302 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +99 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +211 -0
- package/dist/module.js.map +1 -0
- package/dist/orm.d.ts +102 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/orm.js +221 -0
- package/dist/orm.js.map +1 -0
- package/dist/pydantic.d.ts +157 -0
- package/dist/pydantic.d.ts.map +1 -0
- package/dist/pydantic.js +318 -0
- package/dist/pydantic.js.map +1 -0
- package/dist/pytest-config.d.ts +60 -0
- package/dist/pytest-config.d.ts.map +1 -0
- package/dist/pytest-config.js +147 -0
- package/dist/pytest-config.js.map +1 -0
- package/dist/routes.d.ts +249 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +511 -0
- package/dist/routes.js.map +1 -0
- package/package.json +34 -0
- package/src/extract.py +1319 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project's own test-collection rules, read rather than assumed.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `test_*.py`, `Test*`, `test_*` are pytest's **defaults**, and a project can
|
|
7
|
+
* change all three. Hard-coding them is the same mistake as matching a class
|
|
8
|
+
* name to decide it is a test suite: approximating a framework's rule when the
|
|
9
|
+
* framework will state it. The standing rule is *if the framework will tell you,
|
|
10
|
+
* never infer it* — and here it will, in a file already sitting in the
|
|
11
|
+
* repository.
|
|
12
|
+
*
|
|
13
|
+
* Not hypothetical: `threeclouds-ai` declares `[tool.pytest.ini_options]` with
|
|
14
|
+
* `testpaths`, and nothing read it.
|
|
15
|
+
*
|
|
16
|
+
* ## What is read, and what is deliberately not
|
|
17
|
+
*
|
|
18
|
+
* Only the three keys that decide **what is a test**: `python_files`,
|
|
19
|
+
* `python_classes`, `python_functions`. `testpaths` is *not* read: it narrows
|
|
20
|
+
* where pytest looks, and narrowing what Descry analyses on the strength of a
|
|
21
|
+
* test-runner setting would silently shrink the graph for a reason that has
|
|
22
|
+
* nothing to do with the graph.
|
|
23
|
+
*
|
|
24
|
+
* ## Precedence
|
|
25
|
+
*
|
|
26
|
+
* pytest's own order for choosing its config file, and only the first one that
|
|
27
|
+
* carries a section counts — a `pytest.ini` wins outright even when empty of
|
|
28
|
+
* these keys, exactly as it does for pytest.
|
|
29
|
+
*
|
|
30
|
+
* ## The parsing, and its honest limit
|
|
31
|
+
*
|
|
32
|
+
* Zero runtime dependencies, so there is no TOML parser here. This reads one
|
|
33
|
+
* known section and three known keys whose values are a string or an array of
|
|
34
|
+
* strings, which is the whole of what these keys can be. A key it cannot parse
|
|
35
|
+
* leaves the default in place — the failure direction is "behaves as before",
|
|
36
|
+
* never "collects something strange".
|
|
37
|
+
*/
|
|
38
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
39
|
+
import { join } from "node:path";
|
|
40
|
+
export const PYTEST_DEFAULTS = {
|
|
41
|
+
filePatterns: ["test_*.py", "*_test.py"],
|
|
42
|
+
classPrefixes: ["Test"],
|
|
43
|
+
functionPrefixes: ["test"],
|
|
44
|
+
source: "pytest defaults — no configuration file declares collection rules",
|
|
45
|
+
};
|
|
46
|
+
/** `test_*.py` -> a regex anchored at both ends. Only `*` and `?` are glob here. */
|
|
47
|
+
export function globToRegExp(glob) {
|
|
48
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
49
|
+
return new RegExp(`^${escaped}$`);
|
|
50
|
+
}
|
|
51
|
+
/** Values of `key` in an INI-style section body: whitespace or newline separated. */
|
|
52
|
+
function iniValues(body, key) {
|
|
53
|
+
const match = new RegExp(`^[ \\t]*${key}[ \\t]*=([^\\n]*(?:\\n[ \\t]+[^\\n]*)*)`, "m").exec(body);
|
|
54
|
+
if (match === null)
|
|
55
|
+
return undefined;
|
|
56
|
+
const values = match[1].split(/\s+/).filter((value) => value !== "");
|
|
57
|
+
return values.length > 0 ? values : undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Values of `key` in a TOML table body: a string, or an array of strings.
|
|
61
|
+
*
|
|
62
|
+
* **A bare string is a whitespace-separated list, not one pattern.** These three
|
|
63
|
+
* keys are pytest `args`-type options, and pytest splits their value on
|
|
64
|
+
* whitespace whichever file it came from. Sentry writes
|
|
65
|
+
*
|
|
66
|
+
* python_files = "test_*.py sentry/testutils/*"
|
|
67
|
+
*
|
|
68
|
+
* and reading that as a single glob matches no file that can exist. Measured on
|
|
69
|
+
* the real repository: **7,955 files analysed, 2,593 files named `test_*.py`,
|
|
70
|
+
* and zero test cases emitted.** The INI reader has always split correctly; only
|
|
71
|
+
* this one did not, so the bug was invisible to every project that configures
|
|
72
|
+
* pytest anywhere but `pyproject.toml`.
|
|
73
|
+
*
|
|
74
|
+
* Splitting an *array's* elements too is deliberate — `["test_*.py *_test.py"]`
|
|
75
|
+
* is legal and means the same thing to pytest.
|
|
76
|
+
*/
|
|
77
|
+
function tomlValues(body, key) {
|
|
78
|
+
const match = new RegExp(`^[ \\t]*${key}[ \\t]*=[ \\t]*(\\[[^\\]]*\\]|"[^"]*"|'[^']*')`, "m").exec(body);
|
|
79
|
+
if (match === null)
|
|
80
|
+
return undefined;
|
|
81
|
+
const raw = match[1];
|
|
82
|
+
const literals = raw.startsWith("[")
|
|
83
|
+
? [...raw.matchAll(/["']([^"']*)["']/g)].map((m) => m[1])
|
|
84
|
+
: [raw.slice(1, -1)];
|
|
85
|
+
const values = literals.flatMap((literal) => literal.split(/\s+/)).filter((value) => value !== "");
|
|
86
|
+
return values.length > 0 ? values : undefined;
|
|
87
|
+
}
|
|
88
|
+
/** The body of a section, from its header to the next one. */
|
|
89
|
+
function sectionBody(text, header) {
|
|
90
|
+
const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
91
|
+
const start = new RegExp(`^\\[${escaped}\\][ \\t]*$`, "m").exec(text);
|
|
92
|
+
if (start === null)
|
|
93
|
+
return undefined;
|
|
94
|
+
const after = text.slice(start.index + start[0].length);
|
|
95
|
+
const next = /^\[/m.exec(after);
|
|
96
|
+
return next === null ? after : after.slice(0, next.index);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read the collection rules a repository declares, or pytest's defaults.
|
|
100
|
+
*
|
|
101
|
+
* Order is pytest's: `pytest.ini`, then `pyproject.toml`, then `tox.ini`, then
|
|
102
|
+
* `setup.cfg`. The first file carrying the section wins outright, whether or not
|
|
103
|
+
* it sets any of the three keys — narrowing to "the first file that happens to
|
|
104
|
+
* set the key I want" would silently blend two projects' configurations.
|
|
105
|
+
*/
|
|
106
|
+
export function readPytestConfig(absoluteRoot) {
|
|
107
|
+
const candidates = [
|
|
108
|
+
{ file: "pytest.ini", header: "pytest", toml: false },
|
|
109
|
+
{ file: "pyproject.toml", header: "tool.pytest.ini_options", toml: true },
|
|
110
|
+
{ file: "tox.ini", header: "pytest", toml: false },
|
|
111
|
+
{ file: "setup.cfg", header: "tool:pytest", toml: false },
|
|
112
|
+
];
|
|
113
|
+
for (const { file, header, toml } of candidates) {
|
|
114
|
+
const path = join(absoluteRoot, file);
|
|
115
|
+
if (!existsSync(path))
|
|
116
|
+
continue;
|
|
117
|
+
let text;
|
|
118
|
+
try {
|
|
119
|
+
text = readFileSync(path, "utf8");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const body = sectionBody(text, header);
|
|
125
|
+
if (body === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
const read = toml ? tomlValues : iniValues;
|
|
128
|
+
const filePatterns = read(body, "python_files");
|
|
129
|
+
const classPrefixes = read(body, "python_classes");
|
|
130
|
+
const functionPrefixes = read(body, "python_functions");
|
|
131
|
+
const declared = [
|
|
132
|
+
filePatterns === undefined ? null : "python_files",
|
|
133
|
+
classPrefixes === undefined ? null : "python_classes",
|
|
134
|
+
functionPrefixes === undefined ? null : "python_functions",
|
|
135
|
+
].filter((key) => key !== null);
|
|
136
|
+
return {
|
|
137
|
+
filePatterns: filePatterns ?? PYTEST_DEFAULTS.filePatterns,
|
|
138
|
+
classPrefixes: classPrefixes ?? PYTEST_DEFAULTS.classPrefixes,
|
|
139
|
+
functionPrefixes: functionPrefixes ?? PYTEST_DEFAULTS.functionPrefixes,
|
|
140
|
+
source: declared.length === 0
|
|
141
|
+
? `${file} configures pytest but declares no collection rules, so the defaults apply`
|
|
142
|
+
: `${file} declares ${declared.join(", ")}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return PYTEST_DEFAULTS;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=pytest-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pytest-config.js","sourceRoot":"","sources":["../src/pytest-config.ts"],"names":[],"mappings":"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"}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP-route framework extractor for Python — FastAPI, Flask and Django.
|
|
3
|
+
*
|
|
4
|
+
* **An `API_ROUTE` is a framework claim, and this file is where it is earned.**
|
|
5
|
+
* Until now the only producer of a route in this project was `adapter-openapi`,
|
|
6
|
+
* reading a published contract. That is the right first move — *if the framework
|
|
7
|
+
* will tell you, never infer it* — but a repository that publishes no contract
|
|
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:
|
|
21
|
+
*
|
|
22
|
+
* | repository | verb-named decorators | actually routes |
|
|
23
|
+
* | --- | --- | --- |
|
|
24
|
+
* | django | 171 `@mock.patch` | **0** |
|
|
25
|
+
* | saleor | 1,150 `@mock.patch` | **0** |
|
|
26
|
+
* | dispatch | 1 `@app.options` on a Slack Bolt app | **0** |
|
|
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
|
|
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.
|
|
63
|
+
*/
|
|
64
|
+
import type { PyCall } from "./extract.ts";
|
|
65
|
+
export type RouteFramework = "fastapi" | "flask" | "django";
|
|
66
|
+
/**
|
|
67
|
+
* The constructors that make a receiver a router, and the modules they must
|
|
68
|
+
* come from. The module check is the precision control: a local class called
|
|
69
|
+
* `Blueprint` is not Flask's, and matching the bare name would admit it.
|
|
70
|
+
*/
|
|
71
|
+
export declare const ROUTER_CONSTRUCTORS: Readonly<Record<RouteFramework, Readonly<Record<string, ReadonlySet<string>>>>>;
|
|
72
|
+
/** `django.urls.path`/`re_path`/`url`, and `include` for a nested urlconf. */
|
|
73
|
+
export declare const DJANGO_URL_MODULES: ReadonlySet<string>;
|
|
74
|
+
/** HTTP methods a decorator attribute may name. `trace` is included; `head` is. */
|
|
75
|
+
export declare const HTTP_METHODS: ReadonlySet<string>;
|
|
76
|
+
/**
|
|
77
|
+
* FastAPI and Flask both spell "any method, path given by argument" as
|
|
78
|
+
* `.route(...)`; FastAPI additionally spells it `.api_route(...)`.
|
|
79
|
+
*/
|
|
80
|
+
export declare const GENERIC_ROUTE_ATTRS: ReadonlySet<string>;
|
|
81
|
+
/** Attributes that mount one router inside another. */
|
|
82
|
+
export declare const MOUNT_ATTRS: ReadonlySet<string>;
|
|
83
|
+
/** A router as the source declares it. */
|
|
84
|
+
export interface RouterDecl {
|
|
85
|
+
readonly file: string;
|
|
86
|
+
readonly local: string;
|
|
87
|
+
readonly framework: RouteFramework;
|
|
88
|
+
/** The constructor that made it — `APIRouter`, `FastAPI`, `Blueprint`, `Flask`. */
|
|
89
|
+
readonly constructor: string;
|
|
90
|
+
/** `""` when none is written; `null` when one is written and unreadable. */
|
|
91
|
+
readonly prefix: string | null;
|
|
92
|
+
readonly line: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Is this the application itself rather than a router that must be mounted?
|
|
96
|
+
*
|
|
97
|
+
* The distinction decides whether "nothing mounts this" is a disclosure or a
|
|
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.
|
|
101
|
+
*/
|
|
102
|
+
export declare function isApplicationRouter(router: RouterDecl): boolean;
|
|
103
|
+
/** One router mounted inside another. */
|
|
104
|
+
export interface RouterMount {
|
|
105
|
+
readonly file: string;
|
|
106
|
+
/** The scope the mount call is written in — `[]` at module level. */
|
|
107
|
+
readonly scope: readonly string[];
|
|
108
|
+
readonly parentLocal: string;
|
|
109
|
+
readonly childLocal: string;
|
|
110
|
+
readonly prefix: string | null;
|
|
111
|
+
readonly line: number;
|
|
112
|
+
}
|
|
113
|
+
/** One route declaration, before its prefix chain is resolved. */
|
|
114
|
+
export interface RouteDecl {
|
|
115
|
+
readonly file: string;
|
|
116
|
+
/** The scope the decorator is evaluated in — `[]` at module level. */
|
|
117
|
+
readonly scope: readonly string[];
|
|
118
|
+
readonly routerLocal: string;
|
|
119
|
+
readonly methods: readonly string[];
|
|
120
|
+
/** `null` when the path is written as anything but a literal. */
|
|
121
|
+
readonly path: string | null;
|
|
122
|
+
/** The source text of the path argument, for the ledger row. */
|
|
123
|
+
readonly rawPath: string;
|
|
124
|
+
readonly line: number;
|
|
125
|
+
/** The handler this decorator sits on, where there is one. */
|
|
126
|
+
readonly handler: readonly string[] | null;
|
|
127
|
+
/** `response_model=` as written, so `list[OrderRead]` is not lost. */
|
|
128
|
+
readonly responseModel: string | null;
|
|
129
|
+
}
|
|
130
|
+
/** Which framework, if any, a constructor call belongs to. */
|
|
131
|
+
export declare function frameworkOfConstructor(callee: string, moduleOfImport: (name: string) => string | undefined): {
|
|
132
|
+
framework: RouteFramework;
|
|
133
|
+
constructor: string;
|
|
134
|
+
} | null;
|
|
135
|
+
/**
|
|
136
|
+
* Routers declared in one file, by the local name that holds them.
|
|
137
|
+
*
|
|
138
|
+
* `moduleOfImport` answers "which module did this name come from", which is the
|
|
139
|
+
* only thing separating Flask's `Blueprint` from a class of the same name.
|
|
140
|
+
*/
|
|
141
|
+
export declare function routersIn(file: string, calls: readonly PyCall[], assignedLocal: (call: PyCall) => string | undefined, moduleOfImport: (name: string) => string | undefined): RouterDecl[];
|
|
142
|
+
/** Mounts declared in one file. */
|
|
143
|
+
export declare function mountsIn(file: string, calls: readonly PyCall[]): RouterMount[];
|
|
144
|
+
/**
|
|
145
|
+
* Route declarations from one decorator call.
|
|
146
|
+
*
|
|
147
|
+
* Returns `null` when the decorator is not a route at all. The receiver is
|
|
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
|
|
150
|
+
* decided from this file alone.
|
|
151
|
+
*/
|
|
152
|
+
export declare function routeFromDecorator(file: string, call: PyCall, handler: readonly string[] | null): RouteDecl | null;
|
|
153
|
+
export interface OdooRouteEntry {
|
|
154
|
+
readonly file: string;
|
|
155
|
+
readonly scope: readonly string[];
|
|
156
|
+
/** `null` when the path is written as anything but a literal. */
|
|
157
|
+
readonly path: string | null;
|
|
158
|
+
readonly rawPath: string;
|
|
159
|
+
/** `null` when `methods=` is written but unreadable. Absent keyword defaults to GET, Odoo's own documented default. */
|
|
160
|
+
readonly methods: readonly string[] | null;
|
|
161
|
+
readonly line: number;
|
|
162
|
+
readonly handler: readonly string[] | null;
|
|
163
|
+
}
|
|
164
|
+
/** `@http.route(...)` / `@route(...)`, verified against `odoo.http`. */
|
|
165
|
+
export declare function odooRouteFromDecorator(file: string, call: PyCall, handler: readonly string[] | null, moduleOfImport: (name: string) => string | undefined): OdooRouteEntry | null;
|
|
166
|
+
/** DRF's own router constructors — `rest_framework.routers`. */
|
|
167
|
+
export declare const DRF_ROUTER_CONSTRUCTORS: ReadonlySet<string>;
|
|
168
|
+
/**
|
|
169
|
+
* DRF's fixed action table — what `DefaultRouter`/`SimpleRouter` always
|
|
170
|
+
* generate for a registered viewset, unconditionally. This is the router's
|
|
171
|
+
* own documented contract, not an inference about which actions a viewset
|
|
172
|
+
* implements: an unimplemented action still gets the URL, and 405s at
|
|
173
|
+
* request time. Reading the contract rather than the implementation is the
|
|
174
|
+
* same trade this file already makes for Flask's undecorated `.route()`
|
|
175
|
+
* default (`GENERIC_ROUTE_ATTRS`, above).
|
|
176
|
+
*/
|
|
177
|
+
export declare const DRF_ACTIONS: readonly {
|
|
178
|
+
readonly method: string;
|
|
179
|
+
readonly suffix: string;
|
|
180
|
+
}[];
|
|
181
|
+
export declare function djangoUrlFunctionOf(callee: string, moduleOfImport: (name: string) => string | undefined): string | null;
|
|
182
|
+
/**
|
|
183
|
+
* `re_path`/`url` write a param as `(?P<name>...)`, not `<type:name>`.
|
|
184
|
+
*
|
|
185
|
+
* Translating an arbitrary regex into a path template is not attempted —
|
|
186
|
+
* that is the same guess `templateOfSource` in `client.ts` refuses to make
|
|
187
|
+
* for a call path, for the same reason. What is read: the anchors (`^`/`$`,
|
|
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.
|
|
193
|
+
*/
|
|
194
|
+
export declare function djangoRegexPathOf(raw: string): string | null;
|
|
195
|
+
/** One `path()`/`re_path()`/`url()` call, read but not yet chain-resolved. */
|
|
196
|
+
export interface DjangoRouteEntry {
|
|
197
|
+
readonly file: string;
|
|
198
|
+
readonly scope: readonly string[];
|
|
199
|
+
/** `null` when the path could not be read — a ledger row, not a guess. */
|
|
200
|
+
readonly path: string | null;
|
|
201
|
+
readonly rawPath: string;
|
|
202
|
+
readonly line: number;
|
|
203
|
+
/** The view argument's source text, for the handler-resolution pass. */
|
|
204
|
+
readonly viewText: string;
|
|
205
|
+
/** Set when the view is written `X.as_view()` — `X` is the CBV to inspect for verb methods. */
|
|
206
|
+
readonly viewClassLocal: string | null;
|
|
207
|
+
}
|
|
208
|
+
/** One `path(prefix, include("other.urls"))` — a mount, keyed by module string rather than by local. */
|
|
209
|
+
export interface DjangoIncludeEntry {
|
|
210
|
+
readonly file: string;
|
|
211
|
+
readonly scope: readonly string[];
|
|
212
|
+
/** `""` when no prefix is written; `null` when one is written and unreadable. */
|
|
213
|
+
readonly prefix: string | null;
|
|
214
|
+
/** The dotted module `include(...)` names, or `null` when it names anything else. */
|
|
215
|
+
readonly targetModule: string | null;
|
|
216
|
+
readonly rawTarget: string;
|
|
217
|
+
readonly line: number;
|
|
218
|
+
}
|
|
219
|
+
/** Every `path()`/`re_path()`/`url()` call in one file, split into routes and includes. */
|
|
220
|
+
export declare function djangoUrlEntriesIn(file: string, calls: readonly PyCall[], moduleOfImport: (name: string) => string | undefined): {
|
|
221
|
+
readonly routes: DjangoRouteEntry[];
|
|
222
|
+
readonly includes: DjangoIncludeEntry[];
|
|
223
|
+
};
|
|
224
|
+
/** `router = DefaultRouter()` / `SimpleRouter()`, verified against `rest_framework.routers`. */
|
|
225
|
+
export declare function drfRouterConstructorOf(callee: string, moduleOfImport: (name: string) => string | undefined): boolean;
|
|
226
|
+
/** One `router.register(prefix, ViewSetClass)` call. */
|
|
227
|
+
export interface DrfRegisterEntry {
|
|
228
|
+
readonly file: string;
|
|
229
|
+
readonly routerLocal: string;
|
|
230
|
+
readonly scope: readonly string[];
|
|
231
|
+
/** `null` when the prefix is written but unreadable. */
|
|
232
|
+
readonly prefix: string | null;
|
|
233
|
+
readonly rawPrefix: string;
|
|
234
|
+
readonly viewSetText: string;
|
|
235
|
+
readonly line: number;
|
|
236
|
+
}
|
|
237
|
+
/** Locals bound to a DRF router construction — `router = DefaultRouter()`. */
|
|
238
|
+
export declare function drfRouterLocalsIn(calls: readonly PyCall[], localOfCall: (call: PyCall) => string | undefined, moduleOfImport: (name: string) => string | undefined): ReadonlySet<string>;
|
|
239
|
+
/** Every `.register(...)` call on a receiver, whether or not it is a DRF router — the caller filters. */
|
|
240
|
+
export declare function drfRegisterCallsIn(file: string, calls: readonly PyCall[]): DrfRegisterEntry[];
|
|
241
|
+
/**
|
|
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.
|
|
247
|
+
*/
|
|
248
|
+
export declare function joinPath(prefix: string, path: string): string;
|
|
249
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CACxC,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAWtE,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAIjD,CAAC;AAEH,mFAAmF;AACnF,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,CAS3C,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAKlD,CAAC;AAEH,uDAAuD;AACvD,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CAI1C,CAAC;AAEH,0CAA0C;AAC1C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,mFAAmF;IACnF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAE/D;AAED,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3C,sEAAsE;IACtE,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,8DAA8D;AAC9D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnD;IAAE,SAAS,EAAE,cAAc,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsB3D;AAYD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EACnD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnD,UAAU,EAAE,CAkBd;AAED,mCAAmC;AACnC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,EAAE,CA0C9E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAChC,SAAS,GAAG,IAAI,CAuClB;AAeD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uHAAuH;IACvH,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CAC5C;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,EACjC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnD,cAAc,GAAG,IAAI,CAyBvB;AAsBD,gEAAgE;AAChE,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAGtD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAOtF,CAAC;AAuBF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnD,MAAM,GAAG,IAAI,CAEf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5D;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+FAA+F;IAC/F,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,wGAAwG;AACxG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,qFAAqF;IACrF,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnD;IAAE,QAAQ,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAqDlF;AAED,gGAAgG;AAChG,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnD,OAAO,CAOT;AAED,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,EACjD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACnD,WAAW,CAAC,MAAM,CAAC,CAQrB;AAED,yGAAyG;AACzG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAmB7F;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM7D"}
|