@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.
@@ -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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"}
@@ -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":"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"}