@suss/adapter-python 0.3.2-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/LICENSE +205 -0
- package/README.md +166 -0
- package/dist/index.cjs +2364 -0
- package/dist/index.d.cts +459 -0
- package/dist/index.d.ts +459 -0
- package/dist/index.js +2317 -0
- package/grammar/NOTICE +28 -0
- package/grammar/README.md +37 -0
- package/grammar/tree-sitter-python.wasm +0 -0
- package/package.json +64 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
|
|
2
|
+
import { Tree, Node } from 'web-tree-sitter';
|
|
3
|
+
export { Node as PyNode, Tree as PyTree } from 'web-tree-sitter';
|
|
4
|
+
import { ExtractorOptions, RawCodeStructure } from '@suss/extractor';
|
|
5
|
+
import { Database } from '@suss/datalog';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A new `Parser` for every call, so that parses running at the same time do
|
|
9
|
+
* not fight over one parser's mutable `language` field. The compiled `Language`
|
|
10
|
+
* is loaded once per process and shared.
|
|
11
|
+
*/
|
|
12
|
+
declare function parsePython(source: string): Promise<Tree>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The lexical binder. It builds module, class, and function scopes over one
|
|
16
|
+
* file's parse tree and records how each name in each scope came to be bound,
|
|
17
|
+
* whether that was an import, an assignment, a def, a parameter, or a `global`
|
|
18
|
+
* or `nonlocal` redirect to another scope.
|
|
19
|
+
*
|
|
20
|
+
* It never has to be complete, because "I could not resolve this name" is a
|
|
21
|
+
* legal answer everywhere a name is read. What it has to avoid is being wrong.
|
|
22
|
+
* So it only binds names written directly in a body's own statement list. A
|
|
23
|
+
* definition or import nested inside an `if`, `try`, or `with` block is not
|
|
24
|
+
* found, and a read of it comes back unresolved rather than as a guess.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
type ScopeKind = "module" | "class" | "function";
|
|
28
|
+
interface Scope {
|
|
29
|
+
kind: ScopeKind;
|
|
30
|
+
/** The module node, or the class_definition / function_definition this scope belongs to. */
|
|
31
|
+
node: Node;
|
|
32
|
+
parent: Scope | null;
|
|
33
|
+
bindings: Map<string, Binding>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `relativeLevel` is 0 for an absolute import and the dot count for a
|
|
37
|
+
* relative one. `module` is the dotted path after the dots, empty for
|
|
38
|
+
* a bare `from . import c`.
|
|
39
|
+
*/
|
|
40
|
+
type Binding = {
|
|
41
|
+
kind: "import";
|
|
42
|
+
module: string;
|
|
43
|
+
relativeLevel: number;
|
|
44
|
+
localName: string;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "importFrom";
|
|
47
|
+
module: string;
|
|
48
|
+
relativeLevel: number;
|
|
49
|
+
importedName: string;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "classDef";
|
|
52
|
+
node: Node;
|
|
53
|
+
} | {
|
|
54
|
+
kind: "functionDef";
|
|
55
|
+
node: Node;
|
|
56
|
+
} | {
|
|
57
|
+
kind: "parameter";
|
|
58
|
+
}
|
|
59
|
+
/** The right-hand side, when there is one, so we can trace a decorator's base object one hop back to whatever constructed it. */
|
|
60
|
+
| {
|
|
61
|
+
kind: "assignment";
|
|
62
|
+
value: Node | null;
|
|
63
|
+
}
|
|
64
|
+
/** `global x` inside a function: reads of `x` in this scope resolve in the module scope instead. */
|
|
65
|
+
| {
|
|
66
|
+
kind: "global";
|
|
67
|
+
}
|
|
68
|
+
/** `nonlocal x` inside a function: reads of `x` resolve in the nearest enclosing function scope. */
|
|
69
|
+
| {
|
|
70
|
+
kind: "nonlocal";
|
|
71
|
+
};
|
|
72
|
+
interface ModuleBinding {
|
|
73
|
+
moduleScope: Scope;
|
|
74
|
+
/** The scope a class_definition, a function_definition, or the module node opens, keyed by that node's id. */
|
|
75
|
+
scopeFor: Map<number, Scope>;
|
|
76
|
+
/** The modules a `from X import *` pulls in. We cannot list what a wildcard brings in, so nothing expands them here. */
|
|
77
|
+
openImports: string[];
|
|
78
|
+
}
|
|
79
|
+
declare function bindModule(root: Node): ModuleBinding;
|
|
80
|
+
/**
|
|
81
|
+
* Python does not put a class body's namespace on the lookup chain of a function
|
|
82
|
+
* nested inside it, so a class scope's own bindings only count when the search
|
|
83
|
+
* starts there. A `global` or `nonlocal` marker sends the lookup to the scope it
|
|
84
|
+
* points at.
|
|
85
|
+
*/
|
|
86
|
+
declare function resolveName(scope: Scope, name: string): Binding | null;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Turns a Python annotation into an IR type shape.
|
|
90
|
+
*
|
|
91
|
+
* Nothing here infers anything. A value nobody annotated has no shape at all,
|
|
92
|
+
* and an annotation this module does not recognize comes back as a `ref` by
|
|
93
|
+
* name, which says only what the source called it.
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* `definitions` stores each converted class shape once, however many annotations
|
|
98
|
+
* mention it. `scopeFor` is here so a name written inside a referenced class's
|
|
99
|
+
* body resolves too, not only one written at the annotation's use site.
|
|
100
|
+
*/
|
|
101
|
+
interface AnnotationContext {
|
|
102
|
+
scopeFor: Map<number, Scope>;
|
|
103
|
+
definitions: Map<string, TypeShape | null>;
|
|
104
|
+
}
|
|
105
|
+
/** `scope` is where the annotation is written, which is how we tell a project-local class from an external name. */
|
|
106
|
+
declare function annotationToShape(typeNode: Node, scope: Scope, ctx: AnnotationContext): TypeShape;
|
|
107
|
+
/** Exported so a decorator keyword that gives a class name reads the same way as that name written in annotation position. */
|
|
108
|
+
declare function shapeFromName(name: string, scope: Scope, ctx: AnnotationContext): TypeShape;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Small helpers for reading a tree-sitter-python parse tree.
|
|
112
|
+
*
|
|
113
|
+
* The node-type strings here come from tree-sitter-python's grammar, at the
|
|
114
|
+
* version the grammar README gives. Read a node through these helpers rather
|
|
115
|
+
* than calling `childForFieldName` or `.type` inline, so that a grammar upgrade
|
|
116
|
+
* that renames a field only has to be fixed in one place.
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
interface Range {
|
|
120
|
+
start: number;
|
|
121
|
+
end: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
type DecoratorArg = {
|
|
125
|
+
kind: "string";
|
|
126
|
+
value: string;
|
|
127
|
+
} | {
|
|
128
|
+
kind: "number";
|
|
129
|
+
value: number;
|
|
130
|
+
} | {
|
|
131
|
+
kind: "boolean";
|
|
132
|
+
value: boolean;
|
|
133
|
+
} | {
|
|
134
|
+
kind: "none";
|
|
135
|
+
}
|
|
136
|
+
/** A bare name. The caller resolves it, using the scope it already has. */
|
|
137
|
+
| {
|
|
138
|
+
kind: "identifier";
|
|
139
|
+
name: string;
|
|
140
|
+
} | {
|
|
141
|
+
kind: "list";
|
|
142
|
+
items: DecoratorArg[];
|
|
143
|
+
} | {
|
|
144
|
+
kind: "other";
|
|
145
|
+
};
|
|
146
|
+
interface DecoratorClassification {
|
|
147
|
+
/** The name as its source module exports it, not the local alias or attribute path it was written under. */
|
|
148
|
+
importedName: string | null;
|
|
149
|
+
/** The dotted module the name was imported from. It is null whenever `importedName` is null. */
|
|
150
|
+
module: string | null;
|
|
151
|
+
/** The local variable an attribute decorator hangs on, `app` in `@app.get(...)`. */
|
|
152
|
+
objectName: string | null;
|
|
153
|
+
relativeLevel: number;
|
|
154
|
+
args: DecoratorArg[];
|
|
155
|
+
keywordArgs: Record<string, DecoratorArg>;
|
|
156
|
+
/** Where the decorator is written. Anything we read out of its arguments uses this as its provenance. */
|
|
157
|
+
range: Range;
|
|
158
|
+
}
|
|
159
|
+
declare function classifyDecorator(decoratorNode: Node, scope: Scope): DecoratorClassification;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The Python adapter's own pattern-pack contract.
|
|
163
|
+
*
|
|
164
|
+
* This is deliberately not the TypeScript adapter's `PatternPack`. That type
|
|
165
|
+
* dispatches its discovery variants through ts-morph-specific handlers, and
|
|
166
|
+
* Python's two route shapes have no exact match in its union anyway. Match
|
|
167
|
+
* shapes stay per-language until a second implementation shows what is actually
|
|
168
|
+
* shared, and this is the per-language one for Python.
|
|
169
|
+
*
|
|
170
|
+
* A pack is still plain data, following the same rule the TypeScript packs
|
|
171
|
+
* follow: it describes what a library defines, never anything a project chose.
|
|
172
|
+
*/
|
|
173
|
+
interface PythonPack {
|
|
174
|
+
name: string;
|
|
175
|
+
/** Wire protocol for the produced boundary bindings, e.g. "http". */
|
|
176
|
+
protocol: string;
|
|
177
|
+
discovery: PythonDiscoveryPattern[];
|
|
178
|
+
}
|
|
179
|
+
type PythonDiscoveryPattern = DecoratedClassRoute | DecoratedFunctionRoute;
|
|
180
|
+
/** Conventions both kinds of route share. Each one describes what the library does, never a project's choice. */
|
|
181
|
+
interface RouteConventions {
|
|
182
|
+
/** How the library spells a path parameter. The README lists the syntaxes we know how to read. */
|
|
183
|
+
pathParamSyntax?: string;
|
|
184
|
+
/** Set it only when the library itself binds an annotated local class to the request body. */
|
|
185
|
+
annotatedClassIsRequestBody?: boolean;
|
|
186
|
+
/**
|
|
187
|
+
* Callables the library uses to inject a parameter rather than read it
|
|
188
|
+
* off the request. FastAPI's `Depends` and `Security` are these: the
|
|
189
|
+
* server supplies the value and the client sends nothing, so a
|
|
190
|
+
* parameter defaulted to one of them is no part of the request however
|
|
191
|
+
* its annotation reads.
|
|
192
|
+
*/
|
|
193
|
+
injectedParameterCallees?: string[];
|
|
194
|
+
/**
|
|
195
|
+
* What the library serves when a composed path ends up with repeated
|
|
196
|
+
* slashes in it. Werkzeug serves the merged path and redirects
|
|
197
|
+
* the written one, so "merged" is what Flask needs; "kept" is the
|
|
198
|
+
* default and is what Starlette does.
|
|
199
|
+
*/
|
|
200
|
+
pathRepeatedSlashes?: PathRepeatedSlashes;
|
|
201
|
+
/** The status the library returns for a declared response when the route does not give one. Library-defined. */
|
|
202
|
+
defaultStatusCode?: number;
|
|
203
|
+
/**
|
|
204
|
+
* Set it only when the library reads a status out of the tuple a handler
|
|
205
|
+
* returns, which is what Flask does with `return body, 201`. Without it
|
|
206
|
+
* the library default applies whatever the body returns, and a route
|
|
207
|
+
* that sets its own status would be reported at the default.
|
|
208
|
+
*/
|
|
209
|
+
statusFromReturnedTuple?: boolean;
|
|
210
|
+
/** Unset means the library has no router mounting, and a route's decorator path stands as written. */
|
|
211
|
+
routerComposition?: RouterComposition;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A class has a decorator whose first string-literal argument is the route path,
|
|
215
|
+
* and each method in its body named after an HTTP verb becomes its own unit,
|
|
216
|
+
* with the verb taken from the method name.
|
|
217
|
+
*/
|
|
218
|
+
interface DecoratedClassRoute extends RouteConventions {
|
|
219
|
+
type: "decoratedClassRoute";
|
|
220
|
+
/** The library's own module, plus any wrapper module the person configuring the pack lists alongside it. */
|
|
221
|
+
importModule: string[];
|
|
222
|
+
/** The decorator's name as the library exports it, "route" for flask-restx's `Namespace.route`. */
|
|
223
|
+
decoratorName: string;
|
|
224
|
+
/** Method name written in the class body, mapped to the HTTP verb it dispatches. */
|
|
225
|
+
verbMethodNames: Record<string, string>;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* A function has a decorator whose attribute name is the HTTP verb and whose
|
|
229
|
+
* first string-literal argument is the route path, the `@app.get(path)`
|
|
230
|
+
* convention. The object it hangs on may be imported, or built one hop away by
|
|
231
|
+
* a call to something imported.
|
|
232
|
+
*/
|
|
233
|
+
interface DecoratedFunctionRoute extends RouteConventions {
|
|
234
|
+
type: "decoratedFunctionRoute";
|
|
235
|
+
importModule: string[];
|
|
236
|
+
/** Decorator attribute name, mapped to the HTTP verb it means. */
|
|
237
|
+
verbAttributeNames: Record<string, string>;
|
|
238
|
+
/** Unset leaves the return annotation as the only source for the response shape. */
|
|
239
|
+
responseModelKeyword?: string;
|
|
240
|
+
statusCodeKeyword?: string;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* What a library calls the pieces of router mounting, so a route's served path
|
|
244
|
+
* can be built from the literal prefixes written along the way: the router
|
|
245
|
+
* constructor's own, the one at the call that mounts it, and, where the pack
|
|
246
|
+
* says so, the prefix on the object the mount is called on. What each spelling
|
|
247
|
+
* of a prefix means, and what makes a composition abstain, is the grid in the
|
|
248
|
+
* adapter's README.
|
|
249
|
+
*/
|
|
250
|
+
interface RouterComposition {
|
|
251
|
+
/** Constructor whose call builds a mountable router, FastAPI's `APIRouter`. */
|
|
252
|
+
routerConstructorName: string;
|
|
253
|
+
/** Method that mounts a router onto the app, FastAPI's `include_router`. */
|
|
254
|
+
includeMethodName: string;
|
|
255
|
+
/** One keyword serves the constructor and the mount alike. A library that spells them apart needs two fields here. */
|
|
256
|
+
prefixKeyword: string;
|
|
257
|
+
/** Default "prefixes". */
|
|
258
|
+
mountPrefixEffect?: MountPrefixEffect;
|
|
259
|
+
/** Set it when the library works out a path for a router that gives no prefix. We cannot work that path out, so such routes abstain. */
|
|
260
|
+
constructorPrefixRequired?: boolean;
|
|
261
|
+
/** Default "unreadable". The same setting covers the constructor and the mount. */
|
|
262
|
+
noValuePrefix?: NoValuePrefix;
|
|
263
|
+
/** Default "kept". */
|
|
264
|
+
constructorPrefixTrailingSlash?: PrefixTrailingSlash;
|
|
265
|
+
/**
|
|
266
|
+
* Where the object the mount is called on states a prefix of its
|
|
267
|
+
* own, in front of everything the constructor and the mount state.
|
|
268
|
+
* Unset means it states none, which is FastAPI's behavior: an app
|
|
269
|
+
* serves a mounted router exactly where the two prefixes put it.
|
|
270
|
+
*/
|
|
271
|
+
mountObjectPrefix?: MountObjectPrefix;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The prefix the object a mount is called on states, and where it is
|
|
275
|
+
* written. flask-restx needs both halves: `Api(prefix=...)` states one
|
|
276
|
+
* on the object itself, and the Flask blueprint the `Api` was built
|
|
277
|
+
* from states another with `Blueprint(name, __name__,
|
|
278
|
+
* url_prefix=...)`. The library serves a route under the blueprint's
|
|
279
|
+
* prefix, then the `Api`'s, then whatever the namespace and the route
|
|
280
|
+
* say.
|
|
281
|
+
*/
|
|
282
|
+
interface MountObjectPrefix {
|
|
283
|
+
/** Keyword stating a prefix on the mount object's own construction (flask-restx's `Api(prefix=...)`). */
|
|
284
|
+
prefixKeyword?: string;
|
|
285
|
+
/** The object handed to that construction which states a prefix of its own. */
|
|
286
|
+
carrier?: MountObjectCarrier;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* An object handed to the mount object's constructor, one hop further
|
|
290
|
+
* out, with a prefix of its own (the Flask blueprint behind an
|
|
291
|
+
* `Api`). Naming it here is what lets the adapter tell that object
|
|
292
|
+
* apart from the plain app that appears in the same argument position and
|
|
293
|
+
* has no prefix at all.
|
|
294
|
+
*/
|
|
295
|
+
interface MountObjectCarrier {
|
|
296
|
+
/** Modules the carrier's constructor is imported from (Flask's `flask`). */
|
|
297
|
+
importModule: string[];
|
|
298
|
+
/** Constructor building the carrier, as its library exports it (Flask's `Blueprint`). */
|
|
299
|
+
constructorName: string;
|
|
300
|
+
/** Position of the carrier among the mount object's constructor arguments. */
|
|
301
|
+
argumentIndex: number;
|
|
302
|
+
/** Keyword stating the carrier's prefix, at its construction and at its registration alike (Flask's `url_prefix`). */
|
|
303
|
+
prefixKeyword: string;
|
|
304
|
+
/**
|
|
305
|
+
* Method handing the carrier to an already-built mount object
|
|
306
|
+
* (flask-restx's `init_app`, the application-factory spelling of
|
|
307
|
+
* `Api(blueprint)`). Unset means the constructor argument is the
|
|
308
|
+
* only way in.
|
|
309
|
+
*/
|
|
310
|
+
handoffMethodName?: string;
|
|
311
|
+
/**
|
|
312
|
+
* Method registering the carrier somewhere else (Flask's
|
|
313
|
+
* `register_blueprint`). The adapter reads it only to abstain: a
|
|
314
|
+
* registration restating the prefix, putting the carrier inside
|
|
315
|
+
* another carrier, or happening twice moves the served path
|
|
316
|
+
* somewhere the carrier's own construction no longer says.
|
|
317
|
+
*/
|
|
318
|
+
registerMethodName: string;
|
|
319
|
+
}
|
|
320
|
+
type MountPrefixEffect = "prefixes" | "replaces";
|
|
321
|
+
type PrefixTrailingSlash = "kept" | "trimmed";
|
|
322
|
+
type NoValuePrefix = "unstated" | "unreadable";
|
|
323
|
+
/**
|
|
324
|
+
* Werkzeug serves the merged path and redirects the written one, so Flask
|
|
325
|
+
* needs "merged". Starlette leaves the path as composed, so FastAPI keeps
|
|
326
|
+
* the default.
|
|
327
|
+
*/
|
|
328
|
+
type PathRepeatedSlashes = "kept" | "merged";
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Maps a dotted import onto the file in the repo it refers to.
|
|
332
|
+
*
|
|
333
|
+
* The search walks a small set of configured roots. Finding out what `sys.path`
|
|
334
|
+
* actually is would mean running Python, so when more than one root offers a
|
|
335
|
+
* candidate, the resolver abstains rather than picking one.
|
|
336
|
+
*/
|
|
337
|
+
interface RelativeModuleSpec {
|
|
338
|
+
/** The dotted path after the leading dots. It is empty for a bare `from . import x`. */
|
|
339
|
+
module: string;
|
|
340
|
+
/** Dot count: 0 means absolute, 1 is `.`, 2 is `..`, and so on. */
|
|
341
|
+
relativeLevel: number;
|
|
342
|
+
}
|
|
343
|
+
type ModuleResolution = {
|
|
344
|
+
status: "resolved";
|
|
345
|
+
file: string;
|
|
346
|
+
} | {
|
|
347
|
+
status: "unresolved";
|
|
348
|
+
/**
|
|
349
|
+
* "external": no configured root has a file for it. "ambiguous": more
|
|
350
|
+
* than one does. "outsideRoots": a relative import had enough dots to walk
|
|
351
|
+
* up past every configured root.
|
|
352
|
+
*/
|
|
353
|
+
reason: "external" | "ambiguous" | "outsideRoots";
|
|
354
|
+
};
|
|
355
|
+
interface ModuleResolverOptions {
|
|
356
|
+
/** The directories an absolute dotted import is resolved against. We cannot see `sys.path` order, since that only exists at runtime. */
|
|
357
|
+
roots: string[];
|
|
358
|
+
}
|
|
359
|
+
declare function resolveAbsoluteModule(dotted: string, options: ModuleResolverOptions): ModuleResolution;
|
|
360
|
+
/**
|
|
361
|
+
* The search starts in the importing file's own directory, because Python
|
|
362
|
+
* resolves a relative import against `__package__` and not against `sys.path`.
|
|
363
|
+
* `options.roots` only limits how far the dots can walk up, so a deeply relative
|
|
364
|
+
* import cannot land in an unrelated checkout.
|
|
365
|
+
*/
|
|
366
|
+
declare function resolveRelativeModule(importingFile: string, spec: RelativeModuleSpec, options: ModuleResolverOptions): ModuleResolution;
|
|
367
|
+
declare function resolveModule(importingFile: string, spec: RelativeModuleSpec, options: ModuleResolverOptions): ModuleResolution;
|
|
368
|
+
|
|
369
|
+
/** One file, already parsed and bound. `buildRouterIndex` takes a project as a list of these. */
|
|
370
|
+
interface BoundPythonFile {
|
|
371
|
+
/** The absolute path, which is what module resolution joins on. */
|
|
372
|
+
file: string;
|
|
373
|
+
/** The path a gap refers to this file by, which a reader has to be able to open. */
|
|
374
|
+
displayPath: string;
|
|
375
|
+
root: Node;
|
|
376
|
+
module: ModuleBinding;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* What the object a decorator hangs on turns out to be. `notRouter` covers the
|
|
380
|
+
* app itself and anything the index never saw constructed, so the decorator's
|
|
381
|
+
* own path stands as written. `composed` gives the prefix to put in front of
|
|
382
|
+
* that path. An `abstain` reason is written to follow "the router this route is
|
|
383
|
+
* declared on ...".
|
|
384
|
+
*/
|
|
385
|
+
type RoutePrefixResolution = {
|
|
386
|
+
kind: "notRouter";
|
|
387
|
+
} | {
|
|
388
|
+
kind: "composed";
|
|
389
|
+
value: string;
|
|
390
|
+
} | {
|
|
391
|
+
kind: "abstain";
|
|
392
|
+
reason: string;
|
|
393
|
+
};
|
|
394
|
+
interface RouterIndex {
|
|
395
|
+
resolve(pattern: PythonDiscoveryPattern, module: ModuleBinding, objectName: string): RoutePrefixResolution;
|
|
396
|
+
}
|
|
397
|
+
declare function buildRouterIndex(files: BoundPythonFile[], packs: PythonPack[], resolverOptions: ModuleResolverOptions): RouterIndex;
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Finds decorated routes and turns each one into a `RawCodeStructure`.
|
|
401
|
+
*
|
|
402
|
+
* Only decorated module-level functions and classes that are declared
|
|
403
|
+
* unconditionally get discovered, the same boundary the binder in scope.ts
|
|
404
|
+
* draws. Nothing here reads into a unit's body, so a route's summary comes out
|
|
405
|
+
* with no branches at all, or with exactly one branch describing what an
|
|
406
|
+
* annotation or a decorator keyword already states, such as a FastAPI
|
|
407
|
+
* `response_model` or `status_code`, or a return annotation.
|
|
408
|
+
*
|
|
409
|
+
* That is enough to pair a route against a caller by method and path, and it
|
|
410
|
+
* claims nothing about behavior nobody read.
|
|
411
|
+
*/
|
|
412
|
+
|
|
413
|
+
interface DiscoveryOptions {
|
|
414
|
+
packs: PythonPack[];
|
|
415
|
+
/** Repo-relative or absolute path recorded on each summary's `location.file`. */
|
|
416
|
+
filePath: string;
|
|
417
|
+
/** Without it, every route object looks like the app itself and paths stand as written. */
|
|
418
|
+
routerIndex?: RouterIndex;
|
|
419
|
+
/** Under "strict" a route whose unit cannot be built stops the run instead of abstaining. */
|
|
420
|
+
gapHandling?: ExtractorOptions["gapHandling"];
|
|
421
|
+
}
|
|
422
|
+
declare function discoverUnits(root: Node, module: ModuleBinding, options: DiscoveryOptions): RawCodeStructure[];
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* The name is part of the key because the range is measured in lines, two
|
|
426
|
+
* units can start on the same line, and `entry` is a set, so keying on the range
|
|
427
|
+
* alone would drop one of them.
|
|
428
|
+
*/
|
|
429
|
+
declare function unitKey(filePath: string, range: {
|
|
430
|
+
start: number;
|
|
431
|
+
end: number;
|
|
432
|
+
}, name: string): string;
|
|
433
|
+
declare function emitEntryFact(db: Database, filePath: string, range: {
|
|
434
|
+
start: number;
|
|
435
|
+
end: number;
|
|
436
|
+
}, name: string): void;
|
|
437
|
+
/** Module scope only. A function- or class-scoped import is not recorded. */
|
|
438
|
+
declare function emitModuleImportFacts(db: Database, filePath: string, module: ModuleBinding, resolverOptions: ModuleResolverOptions): void;
|
|
439
|
+
|
|
440
|
+
interface ExtractPythonOptions {
|
|
441
|
+
/** Absolute paths of the files to parse and extract. */
|
|
442
|
+
files: string[];
|
|
443
|
+
packs: PythonPack[];
|
|
444
|
+
/** Directories an absolute import is resolved against. */
|
|
445
|
+
roots: string[];
|
|
446
|
+
/** When set, `location.file` on each summary is relativized against this. */
|
|
447
|
+
workspaceRoot?: string;
|
|
448
|
+
/** As well as deciding how much of what nobody could read reaches a summary, "strict" lets a route that cannot be built stop the run. */
|
|
449
|
+
gapHandling?: ExtractorOptions["gapHandling"];
|
|
450
|
+
}
|
|
451
|
+
interface ExtractPythonResult {
|
|
452
|
+
summaries: BehavioralSummary[];
|
|
453
|
+
facts: Database;
|
|
454
|
+
}
|
|
455
|
+
declare function extractPythonProject(options: ExtractPythonOptions): Promise<ExtractPythonResult>;
|
|
456
|
+
/** Every `.py` file under `root`, depth-first, skipping the usual non-source directories. */
|
|
457
|
+
declare function findPythonFiles(root: string): string[];
|
|
458
|
+
|
|
459
|
+
export { type AnnotationContext, type Binding, type BoundPythonFile, type DecoratedClassRoute, type DecoratedFunctionRoute, type DecoratorArg, type DecoratorClassification, type DiscoveryOptions, type ExtractPythonOptions, type ExtractPythonResult, type ModuleBinding, type ModuleResolution, type ModuleResolverOptions, type MountObjectCarrier, type MountObjectPrefix, type MountPrefixEffect, type NoValuePrefix, type PathRepeatedSlashes, type PrefixTrailingSlash, type PythonDiscoveryPattern, type PythonPack, type RelativeModuleSpec, type RouteConventions, type RoutePrefixResolution, type RouterComposition, type RouterIndex, type Scope, type ScopeKind, annotationToShape, bindModule, buildRouterIndex, classifyDecorator, discoverUnits, emitEntryFact, emitModuleImportFacts, extractPythonProject, findPythonFiles, parsePython, resolveAbsoluteModule, resolveModule, resolveName, resolveRelativeModule, shapeFromName, unitKey };
|