@suss/adapter-python 0.33.1 → 0.34.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/README.md +11 -11
- package/dist/index.cjs +7082 -3037
- package/dist/index.d.cts +589 -284
- package/dist/index.d.ts +589 -284
- package/dist/index.js +6501 -2411
- package/grammar/README.md +26 -23
- package/package.json +10 -9
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { TypeShape, WrapperReference, BehavioralSummary } from '@suss/behavioral-ir';
|
|
1
|
+
import { RequestSpellingMetadata, TypeShape, WrapperReference, BehavioralSummary } from '@suss/behavioral-ir';
|
|
2
2
|
import { Tree, Node } from 'web-tree-sitter';
|
|
3
3
|
export { Node as PyNode, Tree as PyTree } from 'web-tree-sitter';
|
|
4
4
|
import { Database } from '@suss/datalog';
|
|
5
|
-
import { RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic } from '@suss/extractor';
|
|
6
|
-
import { WhyExplained } from '@suss/resolution';
|
|
5
|
+
import { RedirectOption, RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic, DeclaredModule, KeptParses } from '@suss/extractor';
|
|
6
|
+
import { UnwrapsByName, WhyExplained } from '@suss/resolution';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Loads the Python grammar into web-tree-sitter and parses a file.
|
|
10
|
+
*
|
|
11
|
+
* tree-sitter runs as WASM, so the adapter needs no native build step.
|
|
12
|
+
* The rest of the adapter reaches the parser only through this module
|
|
13
|
+
* and the node helpers in the `ast` module, so a different Python grammar would
|
|
14
|
+
* be a change here alone.
|
|
15
|
+
*/
|
|
7
16
|
|
|
8
17
|
/**
|
|
9
18
|
* A new `Parser` for every call, so that parses running at the same time do
|
|
@@ -50,7 +59,7 @@ type ModuleResolution = {
|
|
|
50
59
|
reason: "external" | "ambiguous" | "outsideRoots";
|
|
51
60
|
};
|
|
52
61
|
interface ModuleResolverOptions {
|
|
53
|
-
/** The directories an absolute dotted import is resolved against.
|
|
62
|
+
/** The directories an absolute dotted import is resolved against. Their order does not matter, because the order of `sys.path` exists only at run time. */
|
|
54
63
|
roots: string[];
|
|
55
64
|
}
|
|
56
65
|
declare function resolveAbsoluteModule(dotted: string, options: ModuleResolverOptions): ModuleResolution;
|
|
@@ -64,52 +73,50 @@ declare function resolveRelativeModule(importingFile: string, spec: RelativeModu
|
|
|
64
73
|
declare function resolveModule(importingFile: string, spec: RelativeModuleSpec, options: ModuleResolverOptions): ModuleResolution;
|
|
65
74
|
|
|
66
75
|
/**
|
|
67
|
-
* The
|
|
76
|
+
* The contract a Python pack is written against.
|
|
68
77
|
*
|
|
69
|
-
*
|
|
70
|
-
* dispatches its discovery variants through ts-morph
|
|
71
|
-
* Python's two route
|
|
72
|
-
*
|
|
73
|
-
*
|
|
78
|
+
* It is separate from the TypeScript adapter's `PatternPack` on purpose. That
|
|
79
|
+
* type dispatches its discovery variants through handlers built on ts-morph,
|
|
80
|
+
* and Python's two kinds of route have no exact match in its union. Each
|
|
81
|
+
* language keeps its own match patterns until a second implementation shows
|
|
82
|
+
* which parts the two have in common.
|
|
74
83
|
*
|
|
75
|
-
* A pack is
|
|
76
|
-
*
|
|
84
|
+
* A pack is plain data under the same rule the TypeScript packs follow: it
|
|
85
|
+
* describes what a library defines, and nothing a project chose.
|
|
77
86
|
*/
|
|
87
|
+
|
|
78
88
|
interface PythonPack {
|
|
79
89
|
name: string;
|
|
80
90
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
91
|
+
* Part of the cache key. Bump it on any change that affects the units a
|
|
92
|
+
* pack discovers or the summaries it produces. The CLI also adds a hash
|
|
93
|
+
* of the loaded pack file and its config to the key, so a pack run
|
|
94
|
+
* through the CLI invalidates the cache on an edit whether or not it
|
|
95
|
+
* declares a version.
|
|
86
96
|
*/
|
|
87
97
|
version?: string;
|
|
88
98
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* previous answer.
|
|
99
|
+
* Given the `.py` files a run is about to walk, returns the other files
|
|
100
|
+
* in the project this pack reads. Their content goes into the cache key
|
|
101
|
+
* along with the pack's config, so editing one of them re-extracts
|
|
102
|
+
* instead of returning the cached summaries.
|
|
94
103
|
*/
|
|
95
104
|
discoveryInputs?: (files: readonly string[]) => string[];
|
|
96
105
|
/** Wire protocol for the produced boundary bindings, e.g. "http". */
|
|
97
106
|
protocol: string;
|
|
98
107
|
discovery: PythonDiscoveryPattern[];
|
|
99
108
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* a wrapper that resolves to nothing does not match any decorator
|
|
105
|
-
* and never says why (#188).
|
|
109
|
+
* Wrapper modules around the library that a person lists when
|
|
110
|
+
* configuring the pack. The library's own module does not go here. The adapter
|
|
111
|
+
* reports each of these that no file in the run imports, since
|
|
112
|
+
* otherwise it would match no decorator and nothing would say why (#188).
|
|
106
113
|
*/
|
|
107
114
|
projectModules?: string[];
|
|
108
115
|
/** The callables the library gives a project for making a request. */
|
|
109
116
|
clients?: PyClientCall[];
|
|
110
|
-
/** What the library's own database queries look like.
|
|
117
|
+
/** What the library's own database queries look like. DESIGN.md says how one is matched. */
|
|
111
118
|
storage?: StoragePattern[];
|
|
112
|
-
/** Which of the library's calls give back one of a model class.
|
|
119
|
+
/** Which of the library's calls give back one of a model class. DESIGN.md says what a chain of them composes into. */
|
|
113
120
|
models?: PyModelQueries[];
|
|
114
121
|
/** Which of the library's classes return themselves from `__enter__`, so a `with` block gets the object itself. */
|
|
115
122
|
contextManagers?: PyContextManager[];
|
|
@@ -117,12 +124,93 @@ interface PythonPack {
|
|
|
117
124
|
rawSql?: RawSqlPattern[];
|
|
118
125
|
/** The client objects the library hands a project, and the calls on one that reach the database. */
|
|
119
126
|
sqlClients?: SqlClientPattern[];
|
|
127
|
+
/** Functions the library exports that hand back the argument at `argument`, such as a decorator that returns the function it was given. */
|
|
128
|
+
transparentWrappers?: UnwrapsByName[];
|
|
129
|
+
/**
|
|
130
|
+
* The objects the library puts the request on for a route to read,
|
|
131
|
+
* such as Flask's module-level `request`. A route is handed no
|
|
132
|
+
* parameter for them, so without this a value read off one is not
|
|
133
|
+
* known to come from the request.
|
|
134
|
+
*/
|
|
135
|
+
requestObjects?: RequestObject[];
|
|
136
|
+
/** Where a route reads each part of the request, recorded on every route the pack finds. */
|
|
137
|
+
requestSpelling?: RequestSpellingMetadata;
|
|
138
|
+
/** How the library's test runner collects tests, so a PRD scenario can say which test covers it. */
|
|
139
|
+
tests?: PyTestCases[];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* How a test runner finds tests and what it runs around each one. Every
|
|
143
|
+
* name here is one the runner itself defines or documents; a project's
|
|
144
|
+
* own conventions come through `files`, which lists the files to read.
|
|
145
|
+
* DESIGN.md says how the adapter reads each field.
|
|
146
|
+
*/
|
|
147
|
+
interface PyTestCases {
|
|
148
|
+
/** Base-name patterns of the files the runner collects, `*` matching any run of characters: `test_*.py`. */
|
|
149
|
+
filePatterns: string[];
|
|
150
|
+
/** What a test function or method name starts with: `test`. */
|
|
151
|
+
functionPrefix: string;
|
|
152
|
+
/** What a class of tests is named starting with: `Test`. */
|
|
153
|
+
classPrefix: string;
|
|
154
|
+
/** Base classes whose subclasses contain tests whatever the class is called, as dotted names: `unittest.TestCase`. */
|
|
155
|
+
caseBaseClasses: string[];
|
|
156
|
+
/** The functions and methods the runner calls before a test, by where they are written. */
|
|
157
|
+
setUp: PyTestSetUp;
|
|
158
|
+
/** Markers whose string arguments are the fixtures every test under them runs through, as dotted names: `pytest.mark.usefixtures`. */
|
|
159
|
+
usesFixtureMarkers: string[];
|
|
160
|
+
/** Decorators that declare a fixture, as dotted names: `pytest.fixture`. */
|
|
161
|
+
fixtureDecorators: string[];
|
|
162
|
+
/** The keyword on a fixture decorator that renames the fixture: `name`. */
|
|
163
|
+
fixtureNameKeyword: string;
|
|
164
|
+
/** The keyword on a fixture decorator that runs it before every test in its scope: `autouse`. */
|
|
165
|
+
autouseKeyword: string;
|
|
166
|
+
/** Files whose fixtures apply to every test under their directory: `conftest.py`. */
|
|
167
|
+
sharedFixtureFiles: string[];
|
|
168
|
+
/** Decorators that mark a test so it does not run, or runs expecting to fail, as dotted names: `pytest.mark.skip`. */
|
|
169
|
+
skipDecorators: string[];
|
|
170
|
+
/** The module or class variable the runner reads markers from for every test under it: `pytestmark`. */
|
|
171
|
+
markerVariable?: string;
|
|
172
|
+
/** Parameters the runner fills with something other than a fixture, which are never looked up: `self`, `request`. */
|
|
173
|
+
reservedParameters: string[];
|
|
174
|
+
/** How a test replaces something before it runs. */
|
|
175
|
+
mocks: PyTestMocks;
|
|
176
|
+
/** Read only these files, matched on whole path segments from the end. Left out, every collected file is read. */
|
|
177
|
+
files?: string[];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The setup a runner calls before a test, listed in the order it calls
|
|
181
|
+
* them. A method is looked for on the class and then on the project
|
|
182
|
+
* classes it extends, since the setup is often written on a base class.
|
|
183
|
+
*/
|
|
184
|
+
interface PyTestSetUp {
|
|
185
|
+
/** Functions in the test's module that run before any test in it: `setup_module`. */
|
|
186
|
+
module: string[];
|
|
187
|
+
/** Functions in the test's module that run before each test written as a function: `setup_function`. */
|
|
188
|
+
function: string[];
|
|
189
|
+
/** Methods of a class of tests that run before each test in it: `setup_class`, `setup_method`. */
|
|
190
|
+
testClass: string[];
|
|
191
|
+
/** Methods of a `caseBaseClasses` subclass that run before each test in it: `setUpClass`, `setUp`. */
|
|
192
|
+
caseClass: string[];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The calls that replace a module or a member for the length of a test.
|
|
196
|
+
* A patcher called with a dotted string replaces the object at that path,
|
|
197
|
+
* `patch("orders.service.cancel")`. Called with an object and a member
|
|
198
|
+
* name, it replaces that member, `patch.object(OrderService, "cancel")`.
|
|
199
|
+
*/
|
|
200
|
+
interface PyTestMocks {
|
|
201
|
+
/** Patchers imported from a library, as dotted names: `unittest.mock.patch`, `unittest.mock.patch.object`. */
|
|
202
|
+
patchers: string[];
|
|
203
|
+
/** Patchers a fixture hands the test, by the fixture's parameter name and the methods called on it. */
|
|
204
|
+
fixturePatchers: {
|
|
205
|
+
fixture: string;
|
|
206
|
+
methods: string[];
|
|
207
|
+
}[];
|
|
120
208
|
}
|
|
121
209
|
/**
|
|
122
|
-
* A call talks to the database when the method behind it
|
|
123
|
-
* of the library's query types. Matching on the return
|
|
124
|
-
*
|
|
125
|
-
*
|
|
210
|
+
* A call talks to the database when the method behind it declares that it
|
|
211
|
+
* returns one of the library's query types. Matching on the return type finds
|
|
212
|
+
* queries made through a project base class that wraps the library, where a
|
|
213
|
+
* match on the import would find none.
|
|
126
214
|
*/
|
|
127
215
|
interface StoragePattern {
|
|
128
216
|
/** The module a query type is imported from, `sqlalchemy.orm` for a Session. */
|
|
@@ -132,39 +220,37 @@ interface StoragePattern {
|
|
|
132
220
|
/** Chain-ending methods that change what is stored. Anything else reads. */
|
|
133
221
|
writes: string[];
|
|
134
222
|
/**
|
|
135
|
-
* Methods on a query type that touch no rows of their own
|
|
136
|
-
*
|
|
137
|
-
* already records the work, and
|
|
138
|
-
* A call to one of these records nothing.
|
|
223
|
+
* Methods on a query type that touch no rows of their own, and a call to
|
|
224
|
+
* one records nothing. `session.execute(stmt)` runs a statement whose own
|
|
225
|
+
* chain already records the work, and `close` manages the session.
|
|
139
226
|
*/
|
|
140
227
|
recordsNothing?: string[];
|
|
141
228
|
/**
|
|
142
|
-
* Methods whose keywords supply column values
|
|
143
|
-
* `values` in `update(User).where(id=1).values(name="x")`. Their
|
|
144
|
-
*
|
|
145
|
-
* selector.
|
|
229
|
+
* Methods whose keywords supply column values instead of picking rows,
|
|
230
|
+
* `values` in `update(User).where(id=1).values(name="x")`. Their keywords
|
|
231
|
+
* are reported as fields, and every other call's keywords as the selector.
|
|
146
232
|
*/
|
|
147
233
|
valueMethods?: string[];
|
|
148
234
|
/**
|
|
149
|
-
* Functions the library exports that start a query on their own,
|
|
150
|
-
*
|
|
151
|
-
*
|
|
235
|
+
* Functions the library exports that start a query on their own, such as
|
|
236
|
+
* SQLAlchemy 2.0's `select(...)`. A call site imports one directly instead
|
|
237
|
+
* of reaching it through a project class.
|
|
152
238
|
*/
|
|
153
239
|
queryFunctions?: string[];
|
|
154
240
|
/** Which database the library is talking to, for the boundary binding. */
|
|
155
241
|
storageSystem: "postgresql" | "mysql" | "sqlite";
|
|
156
242
|
}
|
|
157
243
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* class declares. SQLAlchemy and SQLModel take the class as an
|
|
161
|
-
* `session.get(User, id)` and `select(User)`,
|
|
162
|
-
*
|
|
244
|
+
* Which of a library's calls give back an instance of the project model
|
|
245
|
+
* class they were passed, so a method called on the result resolves to the
|
|
246
|
+
* one that class declares. SQLAlchemy and SQLModel take the class as an
|
|
247
|
+
* argument, as in `session.get(User, id)` and `select(User)`, where a Rails
|
|
248
|
+
* finder is called on the class itself.
|
|
163
249
|
*/
|
|
164
250
|
interface PyModelQueries {
|
|
165
251
|
/**
|
|
166
|
-
* The names a model's
|
|
167
|
-
* exports, `DeclarativeBase`, or the function that builds one,
|
|
252
|
+
* The names a model's base classes lead back to: a base class the
|
|
253
|
+
* library exports, `DeclarativeBase`, or the function that builds one,
|
|
168
254
|
* `declarative_base`.
|
|
169
255
|
*/
|
|
170
256
|
baseNames: string[];
|
|
@@ -172,16 +258,16 @@ interface PyModelQueries {
|
|
|
172
258
|
givesBack: string[];
|
|
173
259
|
/** Methods that take the model class and give back one of it, with the position it is written at. */
|
|
174
260
|
entryMethods: PyModelEntryMethod[];
|
|
175
|
-
/** Functions the library exports that do the same, called on their own
|
|
261
|
+
/** Functions the library exports that do the same, called on their own instead of on a session. */
|
|
176
262
|
entryFunctions: PyModelEntryFunction[];
|
|
177
263
|
/** The library's own constructors for a field that reaches another model. */
|
|
178
264
|
relationships?: PyRelationshipConstructor[];
|
|
179
265
|
}
|
|
180
266
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
267
|
+
* The callable a model field is assigned from to say it points at another
|
|
268
|
+
* model, as in `items: list[Item] = Relationship(...)`, and where it is
|
|
269
|
+
* imported from. A match needs the import, so a project function with the
|
|
270
|
+
* same name does not count.
|
|
185
271
|
*/
|
|
186
272
|
interface PyRelationshipConstructor {
|
|
187
273
|
/** The module it is imported from, `sqlmodel` or `sqlalchemy.orm`. */
|
|
@@ -202,9 +288,9 @@ interface PyModelEntryFunction {
|
|
|
202
288
|
}
|
|
203
289
|
/**
|
|
204
290
|
* The function a library gives a project for handing the database a
|
|
205
|
-
* statement written as SQL, and where it
|
|
206
|
-
* `text` from `sqlalchemy`. A
|
|
207
|
-
*
|
|
291
|
+
* statement written as SQL, and where it is imported from. SQLAlchemy
|
|
292
|
+
* exports `text` from `sqlalchemy`. A match needs the import, so a local
|
|
293
|
+
* function with the same name does not count.
|
|
208
294
|
*/
|
|
209
295
|
interface RawSqlPattern {
|
|
210
296
|
/** The module the function is imported from. */
|
|
@@ -216,13 +302,13 @@ interface RawSqlPattern {
|
|
|
216
302
|
}
|
|
217
303
|
/**
|
|
218
304
|
* A client object a library hands a project, and the calls on one that
|
|
219
|
-
* reach the database. `RawSqlPattern` matches a function the file
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
305
|
+
* reach the database. `RawSqlPattern` matches a function the file imported.
|
|
306
|
+
* This matches a method called on a value whose class comes from the
|
|
307
|
+
* library, so a client built in one module and called in another still
|
|
308
|
+
* matches.
|
|
223
309
|
*
|
|
224
|
-
* Every field
|
|
225
|
-
*
|
|
310
|
+
* Every field describes the library: the module, the class and method
|
|
311
|
+
* names, and where each method takes its input.
|
|
226
312
|
*/
|
|
227
313
|
interface SqlClientPattern {
|
|
228
314
|
/** The module the client class comes from, `google.cloud.bigquery`. */
|
|
@@ -231,9 +317,9 @@ interface SqlClientPattern {
|
|
|
231
317
|
clientTypes: string[];
|
|
232
318
|
/** The methods that hand the database a statement, and where each takes it. */
|
|
233
319
|
statements?: SqlStatementCall[];
|
|
234
|
-
/** The methods that
|
|
320
|
+
/** The methods that take a table name instead of SQL, and what each does to the table. */
|
|
235
321
|
tables?: SqlTableCall[];
|
|
236
|
-
/** The methods that
|
|
322
|
+
/** The methods that return an instance of another of the library's classes, so a chain can continue through it. */
|
|
237
323
|
handsBack?: SqlClientHandoff[];
|
|
238
324
|
/** Which database the client is talking to, for the boundary binding. */
|
|
239
325
|
storageSystem: string;
|
|
@@ -252,20 +338,20 @@ interface SqlStatementCall extends SqlCallArgument {
|
|
|
252
338
|
/** The method name, `query`. */
|
|
253
339
|
method: string;
|
|
254
340
|
/**
|
|
255
|
-
* The keys
|
|
256
|
-
*
|
|
341
|
+
* The keys to follow when the statement is passed inside a dictionary
|
|
342
|
+
* instead of as the argument itself: `["query", "query"]` for
|
|
257
343
|
* `insert_job(configuration={"query": {"query": sql}})`.
|
|
258
344
|
*/
|
|
259
345
|
path?: string[];
|
|
260
346
|
}
|
|
261
|
-
/** One method that
|
|
347
|
+
/** One method that takes the name of the table it works on, and what it does to that table. */
|
|
262
348
|
interface SqlTableCall extends SqlCallArgument {
|
|
263
349
|
/** The method name, `get_table`. */
|
|
264
350
|
method: string;
|
|
265
351
|
/** Whether the call changes what is stored. */
|
|
266
352
|
kind: "read" | "write";
|
|
267
353
|
}
|
|
268
|
-
/** One method that
|
|
354
|
+
/** One method that returns an instance of another of the library's classes. */
|
|
269
355
|
interface SqlClientHandoff {
|
|
270
356
|
/** The method name, Airflow's `get_client`. */
|
|
271
357
|
method: string;
|
|
@@ -276,10 +362,10 @@ interface SqlClientHandoff {
|
|
|
276
362
|
}
|
|
277
363
|
type PythonDiscoveryPattern = DecoratedClassRoute | DecoratedFunctionRoute;
|
|
278
364
|
/**
|
|
279
|
-
*
|
|
280
|
-
* `with X() as y`
|
|
281
|
-
*
|
|
282
|
-
*
|
|
365
|
+
* Classes whose `__enter__` returns the object it was called on, so
|
|
366
|
+
* `with X() as y` binds `y` to the `X()` the block opened. Python lets
|
|
367
|
+
* `__enter__` return anything, so only the library that wrote the class
|
|
368
|
+
* knows this, and a pack declares it only for its own library's classes.
|
|
283
369
|
*/
|
|
284
370
|
interface PyContextManager {
|
|
285
371
|
/** The module the classes come from, `httpx`. */
|
|
@@ -289,8 +375,8 @@ interface PyContextManager {
|
|
|
289
375
|
}
|
|
290
376
|
/**
|
|
291
377
|
* The callables a library gives a project for making a request. A
|
|
292
|
-
* function that calls one of them
|
|
293
|
-
*
|
|
378
|
+
* function that calls one of them becomes a client unit, bound to the
|
|
379
|
+
* method and path that call is made with.
|
|
294
380
|
*/
|
|
295
381
|
interface PyClientCall {
|
|
296
382
|
type: "clientCall";
|
|
@@ -316,9 +402,9 @@ interface PyClientCall {
|
|
|
316
402
|
response?: PyClientResponse;
|
|
317
403
|
}
|
|
318
404
|
/**
|
|
319
|
-
* The members a library's response object gives a caller. A
|
|
320
|
-
*
|
|
321
|
-
*
|
|
405
|
+
* The members a library's response object gives a caller. A test on one of
|
|
406
|
+
* them shows which statuses the caller handles, and the checker compares
|
|
407
|
+
* that against what the other side sends.
|
|
322
408
|
*/
|
|
323
409
|
interface PyClientResponse {
|
|
324
410
|
/** Members whose value is the status code: `status_code`. */
|
|
@@ -329,63 +415,108 @@ interface PyClientResponse {
|
|
|
329
415
|
body?: string[];
|
|
330
416
|
/** Whether a refused request comes back as a response or raises where it was made. */
|
|
331
417
|
failureDelivery?: "response" | "exception";
|
|
418
|
+
/** Whether the client follows a redirect itself or hands the 3xx back. */
|
|
419
|
+
redirectDelivery?: "followed" | "response";
|
|
420
|
+
/**
|
|
421
|
+
* The keyword argument one call passes to change `redirectDelivery`
|
|
422
|
+
* for itself: `allow_redirects` for requests, `follow_redirects` for
|
|
423
|
+
* httpx.
|
|
424
|
+
*/
|
|
425
|
+
redirectOption?: RedirectOption;
|
|
426
|
+
}
|
|
427
|
+
/** An object a module exports that a route reads the request off. */
|
|
428
|
+
interface RequestObject {
|
|
429
|
+
module: string;
|
|
430
|
+
name: string;
|
|
332
431
|
}
|
|
333
432
|
/** Conventions both kinds of route share. Each one describes what the library does, never a project's choice. */
|
|
334
433
|
interface RouteConventions {
|
|
335
|
-
/** How the library
|
|
434
|
+
/** How the library writes a path parameter. DESIGN.md lists the syntaxes the adapter reads. */
|
|
336
435
|
pathParamSyntax?: string;
|
|
337
436
|
/** Set it only when the library itself binds an annotated local class to the request body. */
|
|
338
437
|
annotatedClassIsRequestBody?: boolean;
|
|
339
438
|
/**
|
|
340
|
-
* Callables the library uses to inject a parameter
|
|
341
|
-
* off the request
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
439
|
+
* Callables the library uses to inject a parameter instead of reading it
|
|
440
|
+
* off the request, such as FastAPI's `Depends` and `Security`. The server
|
|
441
|
+
* supplies the value and the client sends nothing, so a parameter
|
|
442
|
+
* defaulted to one of them is not part of the request, whatever its
|
|
443
|
+
* annotation says.
|
|
345
444
|
*/
|
|
346
445
|
injectedParameterCallees?: string[];
|
|
347
446
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
447
|
+
* Classes the library supplies a parameter for when the parameter is
|
|
448
|
+
* annotated with one, by dotted path, such as FastAPI's
|
|
449
|
+
* `fastapi.Request` and `fastapi.BackgroundTasks`. The client sends
|
|
450
|
+
* nothing for such a parameter either.
|
|
451
|
+
*/
|
|
452
|
+
injectedParameterTypes?: string[];
|
|
453
|
+
/**
|
|
454
|
+
* What the library serves when a composed path has repeated slashes.
|
|
455
|
+
* Werkzeug serves the merged path and redirects the written one, so Flask
|
|
456
|
+
* needs "merged". The default, "kept", matches Starlette.
|
|
352
457
|
*/
|
|
353
458
|
pathRepeatedSlashes?: PathRepeatedSlashes;
|
|
354
|
-
/** The status the library returns for a declared response when the route does not
|
|
459
|
+
/** The status the library returns for a declared response when the route does not set one. */
|
|
355
460
|
defaultStatusCode?: number;
|
|
356
461
|
/**
|
|
357
462
|
* Set it only when the library reads a status out of the tuple a handler
|
|
358
|
-
* returns, as Flask does with `return body, 201`. Without it
|
|
359
|
-
* the library default
|
|
360
|
-
*
|
|
463
|
+
* returns, as Flask does with `return body, 201`. Without it, every return
|
|
464
|
+
* is reported at the library's default status, including a route that
|
|
465
|
+
* sets its own.
|
|
361
466
|
*/
|
|
362
467
|
statusFromReturnedTuple?: boolean;
|
|
363
468
|
/**
|
|
364
469
|
* The library's own callables that end the request with a status, such
|
|
365
|
-
* as FastAPI's `HTTPException` and Flask's `abort`.
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* and one it does cover comes out as the response the library sends.
|
|
470
|
+
* as FastAPI's `HTTPException` and Flask's `abort`. A `raise` of one in a
|
|
471
|
+
* route body becomes the response the library sends. A raise of anything
|
|
472
|
+
* the list does not cover becomes a throw with no status.
|
|
369
473
|
*/
|
|
370
474
|
responseStatusCalls?: PyStatusCall[];
|
|
371
475
|
/**
|
|
372
|
-
* The library's own classes
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
476
|
+
* The library's own classes that are sent as the response when a body
|
|
477
|
+
* returns an instance, and where each takes the status, as in Starlette's
|
|
478
|
+
* `JSONResponse(status_code=...)`. Returning one gives that status, and
|
|
479
|
+
* returning anything else keeps the declared one.
|
|
376
480
|
*/
|
|
377
481
|
responseConstructors?: PyStatusCall[];
|
|
378
482
|
/** Unset means the library has no router mounting, and a route's decorator path is used as written. */
|
|
379
483
|
routerComposition?: RouterComposition;
|
|
380
|
-
/**
|
|
484
|
+
/**
|
|
485
|
+
* The callables that say which part of the request a parameter is read
|
|
486
|
+
* from, by the callee's name, written as the default or inside
|
|
487
|
+
* `Annotated[...]`: FastAPI's `x_tenant_id: str = Header()` is a header.
|
|
488
|
+
*/
|
|
489
|
+
parameterSources?: Record<string, ParameterSource>;
|
|
490
|
+
/** The keyword those callables take a field's name under when it differs from the parameter's, FastAPI's `alias`. */
|
|
491
|
+
parameterAliasKeyword?: string;
|
|
492
|
+
/** The ways the library runs a project's own function around a route. DESIGN.md lists what each one covers. */
|
|
381
493
|
wrappers?: PyWrapperForm[];
|
|
494
|
+
/**
|
|
495
|
+
* The status the library itself responds with, before the handler runs,
|
|
496
|
+
* when a request input the route declares does not validate. FastAPI
|
|
497
|
+
* sends 422. Unset means the library checks nothing on its own.
|
|
498
|
+
*/
|
|
499
|
+
validationFailureStatus?: number;
|
|
500
|
+
/**
|
|
501
|
+
* The number each of the library's status constants is, keyed by the
|
|
502
|
+
* module and name it is imported as (`fastapi.status.HTTP_404_NOT_FOUND`),
|
|
503
|
+
* for a project read without the library installed.
|
|
504
|
+
*/
|
|
505
|
+
statusCodeConstants?: Record<string, number>;
|
|
506
|
+
}
|
|
507
|
+
/** The part of the request a parameter declared with one callable is read from. */
|
|
508
|
+
interface ParameterSource {
|
|
509
|
+
/** The role the parameter gets, `headers` for FastAPI's `Header`. */
|
|
510
|
+
role: string;
|
|
511
|
+
/** What the library writes in place of `_` in the field's name, `-` for a FastAPI header. */
|
|
512
|
+
underscoresAs?: string;
|
|
382
513
|
}
|
|
383
514
|
type PyWrapperForm = PyDependencyForm | PyDecoratedWrapperForm;
|
|
384
515
|
/** Where a wrapper is registered, and which routes the registration reaches. */
|
|
385
516
|
interface PyWrapperRegistrar {
|
|
386
517
|
/** The constructor of the object the registration is written on, as the library exports it: `FastAPI`, `APIRouter`. */
|
|
387
518
|
constructorName: string;
|
|
388
|
-
/** Where the constructor is imported from, when that
|
|
519
|
+
/** Where the constructor is imported from, when that differs from the pattern's own `importModule`: `flask` for the app that serves a flask-restx API. */
|
|
389
520
|
importModule?: string[];
|
|
390
521
|
/**
|
|
391
522
|
* `everyRoute` for the app, whose registration reaches every route of
|
|
@@ -395,13 +526,12 @@ interface PyWrapperRegistrar {
|
|
|
395
526
|
covers: "everyRoute" | "ownRoutes";
|
|
396
527
|
}
|
|
397
528
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* hands on.
|
|
529
|
+
* A project function the library calls before the handler, passed to one
|
|
530
|
+
* of the library's callables, as in FastAPI's `Depends(get_user)`. It can
|
|
531
|
+
* be written as a parameter default, inside `Annotated[...]` on the route,
|
|
532
|
+
* or in a list under `keyword` on the route decorator or a registrar. It
|
|
533
|
+
* ends a request only by raising, so every return passes control on to
|
|
534
|
+
* the route.
|
|
405
535
|
*/
|
|
406
536
|
interface PyDependencyForm {
|
|
407
537
|
type: "dependency";
|
|
@@ -412,11 +542,10 @@ interface PyDependencyForm {
|
|
|
412
542
|
registrars: PyWrapperRegistrar[];
|
|
413
543
|
}
|
|
414
544
|
/**
|
|
415
|
-
* A project function decorated with a method on the app or a router
|
|
416
|
-
* `@app.middleware("http")
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* every return hands the request on.
|
|
545
|
+
* A project function decorated with a method on the app or a router, such
|
|
546
|
+
* as `@app.middleware("http")` or `@app.before_request`. Which of the three
|
|
547
|
+
* optional fields below is set decides what the function's returns mean.
|
|
548
|
+
* With none set, every return passes the request on.
|
|
420
549
|
*/
|
|
421
550
|
interface PyDecoratedWrapperForm {
|
|
422
551
|
type: "decoratedWrapper";
|
|
@@ -430,17 +559,17 @@ interface PyDecoratedWrapperForm {
|
|
|
430
559
|
*/
|
|
431
560
|
continuationParam?: number;
|
|
432
561
|
/**
|
|
433
|
-
* Set when a value
|
|
434
|
-
*
|
|
562
|
+
* Set when returning a value ends the request and only a bare return
|
|
563
|
+
* passes it on. Flask's `before_request` works this way.
|
|
435
564
|
*/
|
|
436
565
|
returnedValueResponds?: boolean;
|
|
437
|
-
/** The position the
|
|
566
|
+
/** The parameter position the raised exception is passed at. Set on an exception handler, which runs only when the route raised. */
|
|
438
567
|
throwParam?: number;
|
|
439
568
|
}
|
|
440
569
|
/**
|
|
441
|
-
* One callable that ends the request, and where it takes the status.
|
|
442
|
-
*
|
|
443
|
-
*
|
|
570
|
+
* One callable that ends the request, and where it takes the status. A
|
|
571
|
+
* call can pass the status by keyword or by position, so a pattern can
|
|
572
|
+
* declare both. When a call writes both, the keyword is used.
|
|
444
573
|
*/
|
|
445
574
|
interface PyStatusCall {
|
|
446
575
|
/** The callee as the file imports it, module and name together, `fastapi.HTTPException`. */
|
|
@@ -453,9 +582,9 @@ interface PyStatusCall {
|
|
|
453
582
|
defaultStatusCode?: number;
|
|
454
583
|
}
|
|
455
584
|
/**
|
|
456
|
-
* A
|
|
457
|
-
*
|
|
458
|
-
* with the verb taken from the method name.
|
|
585
|
+
* A route written as a class whose decorator takes the route path as its
|
|
586
|
+
* first argument. Each method in the class named after an HTTP verb becomes
|
|
587
|
+
* its own unit, with the verb taken from the method name.
|
|
459
588
|
*/
|
|
460
589
|
interface DecoratedClassRoute extends RouteConventions {
|
|
461
590
|
type: "decoratedClassRoute";
|
|
@@ -467,10 +596,10 @@ interface DecoratedClassRoute extends RouteConventions {
|
|
|
467
596
|
verbMethodNames: Record<string, string>;
|
|
468
597
|
}
|
|
469
598
|
/**
|
|
470
|
-
* A
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
* a call to something imported.
|
|
599
|
+
* A route written as a function decorated with `@app.get(path)`: the
|
|
600
|
+
* decorator's attribute name is the HTTP verb and its first argument is the
|
|
601
|
+
* route path. The object the decorator is called on can be imported, or
|
|
602
|
+
* built by a call to something imported.
|
|
474
603
|
*/
|
|
475
604
|
interface DecoratedFunctionRoute extends RouteConventions {
|
|
476
605
|
type: "decoratedFunctionRoute";
|
|
@@ -480,14 +609,21 @@ interface DecoratedFunctionRoute extends RouteConventions {
|
|
|
480
609
|
/** Unset leaves the return annotation as the only source for the response shape. */
|
|
481
610
|
responseModelKeyword?: string;
|
|
482
611
|
statusCodeKeyword?: string;
|
|
612
|
+
/**
|
|
613
|
+
* The keyword whose dictionary lists statuses a route may respond
|
|
614
|
+
* with, FastAPI's `responses`, on the route decorator, its router, a
|
|
615
|
+
* mount call above it or the app. Its keys become the route's declared
|
|
616
|
+
* statuses.
|
|
617
|
+
*/
|
|
618
|
+
responsesKeyword?: string;
|
|
483
619
|
}
|
|
484
620
|
/**
|
|
485
|
-
*
|
|
486
|
-
* can be built from the
|
|
487
|
-
* constructor's
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
621
|
+
* The names a library gives the parts of router mounting, so a route's
|
|
622
|
+
* served path can be built from the prefixes written along the way: the
|
|
623
|
+
* router constructor's, the one on the call that mounts it, and, where the
|
|
624
|
+
* pack declares it, the one on the object the mount is called on. DESIGN.md
|
|
625
|
+
* has the tables for what each way of writing a prefix means and when a
|
|
626
|
+
* route abstains.
|
|
491
627
|
*/
|
|
492
628
|
interface RouterComposition {
|
|
493
629
|
/** Constructor whose call builds a mountable router, FastAPI's `APIRouter`. */
|
|
@@ -495,37 +631,36 @@ interface RouterComposition {
|
|
|
495
631
|
/** Method that mounts a router onto the app, FastAPI's `include_router`. */
|
|
496
632
|
includeMethodName: string;
|
|
497
633
|
/**
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
634
|
+
* The mount method's name for its router parameter, FastAPI's `router`,
|
|
635
|
+
* used to read a call that passes the router by keyword. Unset reads the
|
|
636
|
+
* first positional argument only.
|
|
501
637
|
*/
|
|
502
638
|
routerKeyword?: string;
|
|
503
|
-
/**
|
|
639
|
+
/** The prefix keyword, the same at the constructor and the mount. A library that uses two different keywords would need two fields here. */
|
|
504
640
|
prefixKeyword: string;
|
|
505
|
-
/** Default "prefixes". */
|
|
641
|
+
/** Whether a prefix written at the mount goes in front of the constructor's ("prefixes") or replaces it ("replaces"). Default "prefixes". */
|
|
506
642
|
mountPrefixEffect?: MountPrefixEffect;
|
|
507
|
-
/** Set it when the library
|
|
643
|
+
/** Set it when the library makes up a path of its own for a router constructed with no prefix. The adapter cannot work that path out, so such routes abstain. */
|
|
508
644
|
constructorPrefixRequired?: boolean;
|
|
509
|
-
/**
|
|
645
|
+
/** What a prefix written as `""`, `None`, `False` or `0` means: no prefix ("unstated"), or one the route has to abstain on ("unreadable"). Default "unreadable", at the constructor and the mount alike. */
|
|
510
646
|
noValuePrefix?: NoValuePrefix;
|
|
511
|
-
/** Default "kept". */
|
|
647
|
+
/** Whether the library trims trailing slashes off a constructor's prefix. Default "kept". */
|
|
512
648
|
constructorPrefixTrailingSlash?: PrefixTrailingSlash;
|
|
513
649
|
/**
|
|
514
|
-
* Where the object the mount is called on states a prefix of its
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
650
|
+
* Where the object the mount is called on states a prefix of its own,
|
|
651
|
+
* in front of everything the constructor and the mount state. Unset
|
|
652
|
+
* means it states none. That matches FastAPI, where an app serves a
|
|
653
|
+
* mounted router exactly where the two prefixes put it.
|
|
518
654
|
*/
|
|
519
655
|
mountObjectPrefix?: MountObjectPrefix;
|
|
520
656
|
}
|
|
521
657
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
* say.
|
|
658
|
+
* Where the object a mount is called on states its prefix. flask-restx
|
|
659
|
+
* needs both fields: `Api(prefix=...)` states one on the object itself,
|
|
660
|
+
* and the Flask blueprint the `Api` was built from states another with
|
|
661
|
+
* `Blueprint(name, __name__, url_prefix=...)`. A route is served under
|
|
662
|
+
* the blueprint's prefix, then the `Api`'s, then the namespace's path and
|
|
663
|
+
* the route's own.
|
|
529
664
|
*/
|
|
530
665
|
interface MountObjectPrefix {
|
|
531
666
|
/** Keyword stating a prefix on the mount object's own construction (flask-restx's `Api(prefix=...)`). */
|
|
@@ -534,11 +669,10 @@ interface MountObjectPrefix {
|
|
|
534
669
|
carrier?: MountObjectCarrier;
|
|
535
670
|
}
|
|
536
671
|
/**
|
|
537
|
-
* An object
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* has no prefix at all.
|
|
672
|
+
* An object passed to the mount object's constructor that has a prefix of
|
|
673
|
+
* its own, such as the Flask blueprint an `Api` is built from. Declaring
|
|
674
|
+
* its constructor lets the adapter tell it apart from the plain app, which
|
|
675
|
+
* is passed in the same argument position and has no prefix.
|
|
542
676
|
*/
|
|
543
677
|
interface MountObjectCarrier {
|
|
544
678
|
/** Modules the carrier's constructor is imported from (Flask's `flask`). */
|
|
@@ -547,32 +681,28 @@ interface MountObjectCarrier {
|
|
|
547
681
|
constructorName: string;
|
|
548
682
|
/** Position of the carrier among the mount object's constructor arguments. */
|
|
549
683
|
argumentIndex: number;
|
|
550
|
-
/**
|
|
684
|
+
/** The keyword for the carrier's prefix, at its construction and its registration alike (Flask's `url_prefix`). */
|
|
551
685
|
prefixKeyword: string;
|
|
552
686
|
/**
|
|
553
|
-
* Method
|
|
554
|
-
*
|
|
555
|
-
* `Api(blueprint)
|
|
556
|
-
*
|
|
687
|
+
* Method that passes the carrier to a mount object already built, as
|
|
688
|
+
* flask-restx's `init_app` does in an application factory in place of
|
|
689
|
+
* `Api(blueprint)`. Unset means the constructor argument is the only
|
|
690
|
+
* way to pass one.
|
|
557
691
|
*/
|
|
558
692
|
handoffMethodName?: string;
|
|
559
693
|
/**
|
|
560
|
-
* Method
|
|
561
|
-
* `register_blueprint`). The adapter reads it only to
|
|
562
|
-
* registration
|
|
563
|
-
* another carrier, or
|
|
564
|
-
* somewhere the carrier's
|
|
694
|
+
* Method that registers the carrier somewhere else (Flask's
|
|
695
|
+
* `register_blueprint`). The adapter reads it only to decide when to
|
|
696
|
+
* abstain. A registration that restates the prefix, registers the
|
|
697
|
+
* carrier inside another carrier, or registers it twice serves the
|
|
698
|
+
* routes somewhere other than where the carrier's construction says.
|
|
565
699
|
*/
|
|
566
700
|
registerMethodName: string;
|
|
567
701
|
}
|
|
568
702
|
type MountPrefixEffect = "prefixes" | "replaces";
|
|
569
703
|
type PrefixTrailingSlash = "kept" | "trimmed";
|
|
570
704
|
type NoValuePrefix = "unstated" | "unreadable";
|
|
571
|
-
/**
|
|
572
|
-
* Werkzeug serves the merged path and redirects the written one, so Flask
|
|
573
|
-
* needs "merged". Starlette leaves the path as composed, so FastAPI keeps
|
|
574
|
-
* the default.
|
|
575
|
-
*/
|
|
705
|
+
/** Werkzeug merges repeated slashes, so Flask needs "merged". Starlette does not, so FastAPI keeps "kept". */
|
|
576
706
|
type PathRepeatedSlashes = "kept" | "merged";
|
|
577
707
|
|
|
578
708
|
/**
|
|
@@ -581,11 +711,11 @@ type PathRepeatedSlashes = "kept" | "merged";
|
|
|
581
711
|
* whether that was an import, an assignment, a def, a parameter, or a `global`
|
|
582
712
|
* or `nonlocal` redirect to another scope.
|
|
583
713
|
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
586
|
-
*
|
|
587
|
-
* definition or import nested inside an `if
|
|
588
|
-
*
|
|
714
|
+
* Every reader of a name accepts an unresolved answer, so the binder can
|
|
715
|
+
* leave names out, and a wrong binding is what it avoids. It binds names
|
|
716
|
+
* written directly in a body's statement list, plus the statements of a
|
|
717
|
+
* `with` body. A definition or import nested inside an `if` or `try` is
|
|
718
|
+
* left out, and a read of it comes back unresolved instead of guessed.
|
|
589
719
|
*/
|
|
590
720
|
|
|
591
721
|
type ScopeKind = "module" | "class" | "function";
|
|
@@ -625,7 +755,7 @@ type Binding = {
|
|
|
625
755
|
} | {
|
|
626
756
|
kind: "parameter";
|
|
627
757
|
}
|
|
628
|
-
/** The right-hand side, when there is one, so
|
|
758
|
+
/** The right-hand side, when there is one, so a reader can follow a name one hop back to what constructed it. */
|
|
629
759
|
| {
|
|
630
760
|
kind: "assignment";
|
|
631
761
|
value: Node | null;
|
|
@@ -642,7 +772,7 @@ interface ModuleBinding {
|
|
|
642
772
|
moduleScope: Scope;
|
|
643
773
|
/** The scope a class_definition, a function_definition, or the module node opens, keyed by that node's id. */
|
|
644
774
|
scopeFor: Map<number, Scope>;
|
|
645
|
-
/** The modules a `from X import *` pulls in.
|
|
775
|
+
/** The modules a `from X import *` pulls in. The binder cannot list what a wildcard brings in, so nothing expands them here. */
|
|
646
776
|
openImports: string[];
|
|
647
777
|
}
|
|
648
778
|
declare function bindModule(root: Node): ModuleBinding;
|
|
@@ -654,21 +784,35 @@ declare function bindModule(root: Node): ModuleBinding;
|
|
|
654
784
|
*/
|
|
655
785
|
declare function resolveName(scope: Scope, name: string): Binding | null;
|
|
656
786
|
|
|
787
|
+
/**
|
|
788
|
+
* Works out the prefix a route inherits from the routers it is mounted
|
|
789
|
+
* through, ahead of discovery. With `router = APIRouter(prefix="/items")`
|
|
790
|
+
* and `app.include_router(router, prefix="/api")`, a route on `router` is
|
|
791
|
+
* served under `/api/items`, a path its own file never states. A chain of
|
|
792
|
+
* routers mounted on routers adds a pair of prefixes per hop.
|
|
793
|
+
*
|
|
794
|
+
* Every prefix goes through `readPrefixKeyword` and comes back stated,
|
|
795
|
+
* unstated or unreadable. What each spelling means differs by library, so
|
|
796
|
+
* the pack declares it, and DESIGN.md has the tables. When a path cannot be
|
|
797
|
+
* settled, the route abstains: discovery keeps its name and gives it no
|
|
798
|
+
* path, so it pairs with nothing instead of with a guessed path.
|
|
799
|
+
*/
|
|
800
|
+
|
|
657
801
|
/** One file, already parsed and bound. `buildRouterIndex` takes a project as a list of these. */
|
|
658
802
|
interface BoundPythonFile {
|
|
659
|
-
/** The absolute path
|
|
803
|
+
/** The absolute path. Module resolution matches files on it. */
|
|
660
804
|
file: string;
|
|
661
|
-
/** The path a gap
|
|
805
|
+
/** The path a gap reason prints for this file, so a reader can open it. */
|
|
662
806
|
displayPath: string;
|
|
663
807
|
root: Node;
|
|
664
808
|
module: ModuleBinding;
|
|
665
809
|
}
|
|
666
810
|
/**
|
|
667
|
-
* What the object a decorator
|
|
668
|
-
* app itself and anything the index never saw constructed,
|
|
669
|
-
* own path
|
|
670
|
-
* that path. An `abstain` reason is
|
|
671
|
-
* declared on ...".
|
|
811
|
+
* What the object a route decorator is called on turns out to be. `notRouter`
|
|
812
|
+
* covers the app itself and anything the index never saw constructed, and the
|
|
813
|
+
* decorator's own path is used as written. `composed` gives the prefix to put
|
|
814
|
+
* in front of that path. An `abstain` reason is worded to follow "the router
|
|
815
|
+
* this route is declared on ...".
|
|
672
816
|
*/
|
|
673
817
|
type RoutePrefixResolution = {
|
|
674
818
|
kind: "notRouter";
|
|
@@ -695,6 +839,19 @@ interface RouterIndex {
|
|
|
695
839
|
* built it, and every construction is already keyed by its call.
|
|
696
840
|
*/
|
|
697
841
|
resolveConstruction(pattern: PythonDiscoveryPattern, module: ModuleBinding, constructorName: string, constructionKey: string): RoutePrefixResolution;
|
|
842
|
+
/**
|
|
843
|
+
* The mounts above the router built by this call, innermost first, up
|
|
844
|
+
* to the one on the app. Null unless every router on the way is mounted
|
|
845
|
+
* exactly once by a mount this reading followed, since a route reached
|
|
846
|
+
* two ways runs only the wrappers of the way a request took.
|
|
847
|
+
*/
|
|
848
|
+
mountsAbove(pattern: PythonDiscoveryPattern, constructionKey: string): MountHop[] | null;
|
|
849
|
+
}
|
|
850
|
+
/** One mount above a router: the call that mounts it, and the router it mounts it on, null for the app. */
|
|
851
|
+
interface MountHop {
|
|
852
|
+
call: Node;
|
|
853
|
+
file: string;
|
|
854
|
+
parentKey: string | null;
|
|
698
855
|
}
|
|
699
856
|
interface RouterIndexOptions extends ModuleResolverOptions {
|
|
700
857
|
/** The project's facts, so a loop over a call can be settled by the rules. */
|
|
@@ -716,12 +873,14 @@ declare function buildRouterIndex(files: BoundPythonFile[], packs: PythonPack[],
|
|
|
716
873
|
interface ImportedDefinition {
|
|
717
874
|
/** The class_definition, or the value an assignment gave the name. */
|
|
718
875
|
node: Node;
|
|
876
|
+
/** The file the definition is written in. */
|
|
877
|
+
file: string;
|
|
719
878
|
/** The module scope of the file the definition is written in. */
|
|
720
879
|
moduleScope: Scope;
|
|
721
880
|
/** The scopes of that file, keyed by the node that opens each one. */
|
|
722
881
|
scopeFor: Map<number, Scope>;
|
|
723
882
|
}
|
|
724
|
-
/** `scope` is where the name is read
|
|
883
|
+
/** `scope` is where the name is read. The facts key an import on the file that reads it. */
|
|
725
884
|
type ImportedDefinitionLookup = (scope: Scope, name: string) => ImportedDefinition | null;
|
|
726
885
|
|
|
727
886
|
/**
|
|
@@ -733,11 +892,11 @@ type ImportedDefinitionLookup = (scope: Scope, name: string) => ImportedDefiniti
|
|
|
733
892
|
*/
|
|
734
893
|
|
|
735
894
|
/**
|
|
736
|
-
* `definitions` stores each converted class shape once, however many
|
|
737
|
-
* mention it. `scopeMaps`
|
|
738
|
-
* body
|
|
739
|
-
* read from another file
|
|
740
|
-
* is
|
|
895
|
+
* `definitions` stores each converted class shape once, however many
|
|
896
|
+
* annotations mention it. `scopeMaps` lets a name written inside a
|
|
897
|
+
* referenced class's body resolve as well as one at the annotation, and a
|
|
898
|
+
* class read from another file adds that file's scopes. `importedDefinition`
|
|
899
|
+
* is null when a caller reads one file on its own, and an imported name is
|
|
741
900
|
* then a ref by name and nothing more.
|
|
742
901
|
*/
|
|
743
902
|
interface AnnotationContext {
|
|
@@ -747,7 +906,7 @@ interface AnnotationContext {
|
|
|
747
906
|
/** The alias values being expanded, so `A = B` and `B = A` end rather than recurse. */
|
|
748
907
|
expanding: Set<number>;
|
|
749
908
|
}
|
|
750
|
-
/** `scope` is where the annotation is written
|
|
909
|
+
/** `scope` is where the annotation is written. Resolving the name there tells a project class apart from an external name. */
|
|
751
910
|
declare function annotationToShape(typeNode: Node, scope: Scope, ctx: AnnotationContext): TypeShape;
|
|
752
911
|
/** Exported so a decorator keyword that gives a class name reads the same way as that name written in annotation position. */
|
|
753
912
|
declare function shapeFromName(name: string, scope: Scope, ctx: AnnotationContext): TypeShape;
|
|
@@ -757,7 +916,20 @@ interface Range {
|
|
|
757
916
|
end: number;
|
|
758
917
|
}
|
|
759
918
|
|
|
760
|
-
/**
|
|
919
|
+
/**
|
|
920
|
+
* Works out which library a decorator comes from, for discovery.
|
|
921
|
+
*
|
|
922
|
+
* `@app.route("/x")` is two steps: the call that builds the decorator, and
|
|
923
|
+
* applying the result to the function. Discovery needs only the first, so
|
|
924
|
+
* `classifyDecorator` reads which name the callee resolved to and which
|
|
925
|
+
* module that name came from, and stops there.
|
|
926
|
+
*
|
|
927
|
+
* A decorator re-exported through a project wrapper is handled the same
|
|
928
|
+
* way the TypeScript packs handle one: the name is classified as an import
|
|
929
|
+
* of some module, and the pack lists every module it accepts.
|
|
930
|
+
*/
|
|
931
|
+
|
|
932
|
+
/** An argument as written, plus its node so a reader can evaluate it. */
|
|
761
933
|
type DecoratorArg = DecoratorArgShape & {
|
|
762
934
|
readonly node: Node;
|
|
763
935
|
};
|
|
@@ -790,15 +962,15 @@ type DecoratorArgShape = {
|
|
|
790
962
|
kind: "other";
|
|
791
963
|
};
|
|
792
964
|
interface DecoratorClassification {
|
|
793
|
-
/** The name as its source module exports it,
|
|
965
|
+
/** The name as its source module exports it, whatever alias or attribute path the file wrote it under. */
|
|
794
966
|
importedName: string | null;
|
|
795
967
|
/** The dotted module the name was imported from. It is null whenever `importedName` is null. */
|
|
796
968
|
module: string | null;
|
|
797
|
-
/** The local variable an attribute decorator
|
|
969
|
+
/** The local variable an attribute decorator is called on, `app` in `@app.get(...)`. */
|
|
798
970
|
objectName: string | null;
|
|
799
971
|
args: DecoratorArg[];
|
|
800
972
|
keywordArgs: Record<string, DecoratorArg>;
|
|
801
|
-
/** Where the decorator is written. Anything
|
|
973
|
+
/** Where the decorator is written. Anything read out of its arguments reports this as its location. */
|
|
802
974
|
range: Range;
|
|
803
975
|
/**
|
|
804
976
|
* The module the decorator's object lives in, when the decorator was read
|
|
@@ -807,9 +979,9 @@ interface DecoratorClassification {
|
|
|
807
979
|
*/
|
|
808
980
|
objectModule?: ModuleBinding;
|
|
809
981
|
/**
|
|
810
|
-
* The call the rules say built the object this decorator
|
|
811
|
-
* router
|
|
812
|
-
*
|
|
982
|
+
* The call the rules say built the object this decorator is called on.
|
|
983
|
+
* The router index looks a router up by this call when it never saw the
|
|
984
|
+
* router under a name, as with a decorator on `self.router`.
|
|
813
985
|
*/
|
|
814
986
|
subjectConstruction?: {
|
|
815
987
|
key: string;
|
|
@@ -818,6 +990,13 @@ interface DecoratorClassification {
|
|
|
818
990
|
}
|
|
819
991
|
declare function classifyDecorator(decoratorNode: Node, module: ModuleBinding, facts?: Database): DecoratorClassification;
|
|
820
992
|
|
|
993
|
+
/**
|
|
994
|
+
* Finds the calls in a body that talk to the database, and what each one
|
|
995
|
+
* does. A pack lists its library's query types, and a call chain matches
|
|
996
|
+
* when the method behind it declares that it returns one. DESIGN.md says
|
|
997
|
+
* why the match goes by return type.
|
|
998
|
+
*/
|
|
999
|
+
|
|
821
1000
|
/**
|
|
822
1001
|
* What discovery uses for one file. `factsPath` is the path the facts were
|
|
823
1002
|
* keyed under, which is the absolute one, while a summary shows the short one.
|
|
@@ -826,7 +1005,6 @@ interface StorageLookup {
|
|
|
826
1005
|
readonly facts: Database;
|
|
827
1006
|
readonly factsPath: string;
|
|
828
1007
|
readonly patterns: readonly StoragePattern[];
|
|
829
|
-
readonly definitionAt: (key: string) => Node | undefined;
|
|
830
1008
|
readonly couldMatch: ReadonlySet<string>;
|
|
831
1009
|
/** What a pack says about statements a project writes as SQL itself. */
|
|
832
1010
|
readonly rawSql?: readonly RawSqlPattern[];
|
|
@@ -835,15 +1013,16 @@ interface StorageLookup {
|
|
|
835
1013
|
}
|
|
836
1014
|
|
|
837
1015
|
/**
|
|
838
|
-
* What a call's callee is: a function in this run the walk can
|
|
839
|
-
*
|
|
1016
|
+
* What a call's callee is: a function in this run that the walk can
|
|
1017
|
+
* follow, or the reason it cannot.
|
|
840
1018
|
*
|
|
841
|
-
* The rules in
|
|
842
|
-
*
|
|
843
|
-
*
|
|
1019
|
+
* The rules in `@suss/resolution` settle the callee. They already follow
|
|
1020
|
+
* a value through names, aliases, attributes and instances, so nothing
|
|
1021
|
+
* here reads any of those itself.
|
|
844
1022
|
*
|
|
845
|
-
*
|
|
846
|
-
* dot, and a name only a wildcard import could
|
|
1023
|
+
* This module handles the two cases that depend on files: a module
|
|
1024
|
+
* written before the dot, and a name that only a wildcard import could
|
|
1025
|
+
* have brought in.
|
|
847
1026
|
*/
|
|
848
1027
|
|
|
849
1028
|
/** A function in this run, and the export path its summary gets. */
|
|
@@ -859,16 +1038,16 @@ interface ReachedFunction {
|
|
|
859
1038
|
/**
|
|
860
1039
|
* The project functions a library runs around a route, read from where
|
|
861
1040
|
* they are registered: a dependency on a route's parameter or decorator,
|
|
862
|
-
* a dependency list on the app or a router, a function decorated with
|
|
863
|
-
* `@app.middleware(...)` or `@app.exception_handler(...)`. Each
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
1041
|
+
* a dependency list on the app or a router, or a function decorated with
|
|
1042
|
+
* `@app.middleware(...)` or `@app.exception_handler(...)`. Each becomes a
|
|
1043
|
+
* unit of its own, and every route it reaches lists it, so the extractor
|
|
1044
|
+
* can fold its outcomes into the route's. DESIGN.md, under "What runs
|
|
1045
|
+
* around a route", lists which registrations are read.
|
|
867
1046
|
*
|
|
868
1047
|
* A registration on the app reaches every route of the pack in the run,
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
1048
|
+
* because the run treats a pack's app as the only one. A registration on
|
|
1049
|
+
* a router reaches its own routes, and with a mount call's own list it
|
|
1050
|
+
* reaches those below it when each router on the way is mounted once.
|
|
872
1051
|
*/
|
|
873
1052
|
|
|
874
1053
|
interface WrapperIndexOptions {
|
|
@@ -878,6 +1057,8 @@ interface WrapperIndexOptions {
|
|
|
878
1057
|
/** The function each function key was read from. */
|
|
879
1058
|
definitions: ReadonlyMap<string, Node>;
|
|
880
1059
|
storageFor: (file: BoundPythonFile) => StorageLookup | undefined;
|
|
1060
|
+
/** Where each router is mounted, so a dependency registered on the way reaches the routes below it. */
|
|
1061
|
+
routers?: RouterIndex;
|
|
881
1062
|
}
|
|
882
1063
|
/** The route asking which wrappers reach it: the decorator it was found by, and where that decorator was read. */
|
|
883
1064
|
interface RouteWrapperQuery {
|
|
@@ -894,6 +1075,7 @@ interface Registered {
|
|
|
894
1075
|
reference: WrapperReference;
|
|
895
1076
|
form: PyWrapperForm;
|
|
896
1077
|
}
|
|
1078
|
+
/** A wrapper form with the pattern and pack that declare it. */
|
|
897
1079
|
interface FormOf {
|
|
898
1080
|
pack: PythonPack;
|
|
899
1081
|
pattern: PythonDiscoveryPattern;
|
|
@@ -908,8 +1090,20 @@ declare class PythonWrapperIndex {
|
|
|
908
1090
|
/** By construction key: what a router or a blueprint registered. */
|
|
909
1091
|
private readonly ownRoutes;
|
|
910
1092
|
private readonly unitsByKey;
|
|
1093
|
+
/** By pack name: the app constructions its routes run under. */
|
|
1094
|
+
private readonly appCalls;
|
|
1095
|
+
/** By wrapper reference: the parameters a dependency function declares, and its file. */
|
|
1096
|
+
private readonly dependencyParameterTable;
|
|
911
1097
|
private readonly unitsByFile;
|
|
1098
|
+
/** Told about every registration, so a cache can repeat the ones a file's discovery made. */
|
|
1099
|
+
onRegistered: ((target: ReachedFunction, declared: FormOf) => void) | null;
|
|
912
1100
|
constructor(options: WrapperIndexOptions, filesByPath: ReadonlyMap<string, BoundPythonFile>);
|
|
1101
|
+
/**
|
|
1102
|
+
* The same lookup `wrappersFor` noted under `id`, made again over this
|
|
1103
|
+
* index, or null when `id` is not one of these.
|
|
1104
|
+
*/
|
|
1105
|
+
lookAgain(id: string): string | null;
|
|
1106
|
+
private noted;
|
|
913
1107
|
/** The project function a module-level name in this file refers to, or null for anything else. */
|
|
914
1108
|
functionCalled(file: string, name: string): ReachedFunction | null;
|
|
915
1109
|
register(covers: {
|
|
@@ -921,8 +1115,35 @@ declare class PythonWrapperIndex {
|
|
|
921
1115
|
}, target: ReachedFunction, declared: FormOf): void;
|
|
922
1116
|
/** The unit for a function, built once however many registrations point at it. */
|
|
923
1117
|
registered(target: ReachedFunction, declared: FormOf): Registered;
|
|
1118
|
+
/**
|
|
1119
|
+
* The parameters the dependencies among these wrappers declare, and
|
|
1120
|
+
* whether one is declared through a parameter source such as `Query()`.
|
|
1121
|
+
* The library fills those from the request and validates them, the same
|
|
1122
|
+
* as parameters the route declares.
|
|
1123
|
+
*/
|
|
1124
|
+
dependencyParameters(references: readonly WrapperReference[]): {
|
|
1125
|
+
names: ReadonlySet<string>;
|
|
1126
|
+
declaresSource: boolean;
|
|
1127
|
+
};
|
|
924
1128
|
wrappersFor(query: RouteWrapperQuery): WrapperReference[];
|
|
1129
|
+
/**
|
|
1130
|
+
* What the mounts above the route's router register on the way down:
|
|
1131
|
+
* the dependencies an `include_router` call lists, and those of each
|
|
1132
|
+
* router it is mounted onto, outermost first, the order they run in.
|
|
1133
|
+
*/
|
|
1134
|
+
private mountedAbove;
|
|
1135
|
+
private registeredAbove;
|
|
925
1136
|
private ownRoutesOf;
|
|
1137
|
+
/** The construction call of the router a route is decorated on, when a registrar reads it. */
|
|
1138
|
+
private ownRouterOf;
|
|
1139
|
+
/** Records the app a pack's routes run under, for the keywords written on its construction. */
|
|
1140
|
+
recordApp(pack: string, call: Node, file: string): void;
|
|
1141
|
+
/**
|
|
1142
|
+
* Every call that registers something for a route on the way to it:
|
|
1143
|
+
* the app's construction, the mount calls and the routers they mount
|
|
1144
|
+
* onto, and the route's own router. A caller reads a keyword off each.
|
|
1145
|
+
*/
|
|
1146
|
+
callsAbove(query: RouteWrapperQuery): Node[];
|
|
926
1147
|
/**
|
|
927
1148
|
* Where the decorator's object was built: the route's own file, the file
|
|
928
1149
|
* a project wrapper function is written in, or the file an imported
|
|
@@ -934,30 +1155,29 @@ declare class PythonWrapperIndex {
|
|
|
934
1155
|
}
|
|
935
1156
|
|
|
936
1157
|
/**
|
|
937
|
-
* Finds decorated routes and turns each one into a `RawCodeStructure
|
|
1158
|
+
* Finds decorated routes and turns each one into a `RawCodeStructure`, and
|
|
1159
|
+
* collects the client units for outgoing requests alongside them.
|
|
938
1160
|
*
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
* That is enough to pair a route against a caller by method and path, and it
|
|
947
|
-
* claims nothing about behavior nobody read.
|
|
1161
|
+
* Every decorated definition in the file is classified, wherever it is
|
|
1162
|
+
* written. One a pack's pattern accepts becomes a unit per HTTP method and
|
|
1163
|
+
* per mount. The path is the decorator's first argument with the router
|
|
1164
|
+
* prefixes in front of it. The status and body shape come from decorator
|
|
1165
|
+
* keywords, the return annotation, and each return or raise in the body.
|
|
1166
|
+
* A route that cannot be read keeps its name and gets no path, so it pairs
|
|
1167
|
+
* with nothing.
|
|
948
1168
|
*/
|
|
949
1169
|
|
|
950
1170
|
interface DiscoveryOptions {
|
|
951
1171
|
packs: PythonPack[];
|
|
952
1172
|
/** Repo-relative or absolute path recorded on each summary's `location.file`. */
|
|
953
1173
|
filePath: string;
|
|
954
|
-
/** Without it, every route object
|
|
1174
|
+
/** Without it, every route object is treated as the app itself and paths are used as written. */
|
|
955
1175
|
routerIndex?: RouterIndex;
|
|
956
1176
|
/** Under "strict" a route whose unit cannot be built stops the run instead of abstaining. */
|
|
957
1177
|
gapHandling?: ExtractorOptions["gapHandling"];
|
|
958
|
-
/** What a pack needs to
|
|
1178
|
+
/** What a pack needs to decide that a call talks to the database. Absent when no pack declares storage. */
|
|
959
1179
|
storage?: StorageLookup | undefined;
|
|
960
|
-
/** The file's absolute path, which module resolution
|
|
1180
|
+
/** The file's absolute path, which module resolution needs. `filePath` may be shortened for display. */
|
|
961
1181
|
absoluteFile?: string | undefined;
|
|
962
1182
|
/** The project's facts, so the rules can say what an object no scope has a binding for was built by. */
|
|
963
1183
|
facts?: Database | undefined;
|
|
@@ -968,27 +1188,33 @@ interface DiscoveryOptions {
|
|
|
968
1188
|
}
|
|
969
1189
|
declare function discoverUnits(root: Node, module: ModuleBinding, options: DiscoveryOptions): RawCodeStructure[];
|
|
970
1190
|
|
|
971
|
-
/**
|
|
1191
|
+
/**
|
|
1192
|
+
* Runs the shared resolution rules over this project's facts. The rules in
|
|
1193
|
+
* `@suss/resolution` are the same ones the TypeScript adapter runs, so a
|
|
1194
|
+
* Python value is followed the same way a TypeScript one is.
|
|
1195
|
+
*/
|
|
1196
|
+
|
|
1197
|
+
/** Asks the rules to resolve these keys, and derives what follows. */
|
|
972
1198
|
declare function resolveCalls(db: Database, callKeys: readonly string[]): void;
|
|
973
1199
|
/**
|
|
974
1200
|
* The values an object contains under its own keys, in the order the source
|
|
975
1201
|
* writes them. Empty when nothing said what the object contains.
|
|
976
1202
|
*/
|
|
977
1203
|
declare function containedValues(db: Database, objectKey: string): string[];
|
|
978
|
-
/**
|
|
1204
|
+
/** The object a call returns, when the rules settled it on one. */
|
|
979
1205
|
declare function objectReturnedBy(db: Database, callKey: string): string | null;
|
|
980
1206
|
|
|
981
1207
|
/**
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
* Python value
|
|
1208
|
+
* Emits the value facts that the rules in @suss/resolution join, read from
|
|
1209
|
+
* Python source. The relations are the ones that package defines, so a
|
|
1210
|
+
* Python value is followed by the same rules as a TypeScript one.
|
|
985
1211
|
*
|
|
986
1212
|
* A name is keyed by the scope that binds it. A function's own names take
|
|
987
|
-
* the function's key, so two handlers that both write `query` stay apart
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
*
|
|
991
|
-
*
|
|
1213
|
+
* the function's key, so two handlers that both write `query` stay apart.
|
|
1214
|
+
* A module's names take the file's key, and another file's import matches
|
|
1215
|
+
* on that key. Every write to a name in one scope is collected in source
|
|
1216
|
+
* order, and `valueLeftByWrites` decides what the name comes down to, so a
|
|
1217
|
+
* reassigned name gets one value or none, never two.
|
|
992
1218
|
*/
|
|
993
1219
|
|
|
994
1220
|
/**
|
|
@@ -1015,21 +1241,27 @@ declare function nameKeyIn(filePath: string, enclosing: Node | null, name: strin
|
|
|
1015
1241
|
declare function emitValueFacts(db: Database, filePath: string, root: Node): void;
|
|
1016
1242
|
|
|
1017
1243
|
/**
|
|
1018
|
-
* The session behind `suss ask why` on a Python project
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
1244
|
+
* The session behind `suss ask why` on a Python project. It finds the
|
|
1245
|
+
* expression a question points at by file, line and text, and renders the
|
|
1246
|
+
* witness proof of what that expression resolved to through the phrases
|
|
1247
|
+
* in `@suss/resolution`.
|
|
1022
1248
|
*
|
|
1023
|
-
* It parses every file under the root and
|
|
1024
|
-
* `extractPythonProject` does,
|
|
1025
|
-
* a proof
|
|
1026
|
-
*
|
|
1027
|
-
*
|
|
1249
|
+
* It parses every file under the root and records the same value facts
|
|
1250
|
+
* and pack words `extractPythonProject` does, along with a location for
|
|
1251
|
+
* every fact key, so each step of a proof can point back at the source. A
|
|
1252
|
+
* handle pairs a tree-sitter node with its file, because a node alone
|
|
1253
|
+
* does not say which file it was parsed from.
|
|
1028
1254
|
*/
|
|
1029
1255
|
|
|
1030
1256
|
interface PythonWhySessionOptions {
|
|
1031
1257
|
/** The project root, which paths in every answer come out relative to. */
|
|
1032
1258
|
dir: string;
|
|
1259
|
+
/** Directories an absolute import is resolved against. Read from `dir` when absent, the way `extractPythonProject` reads them. */
|
|
1260
|
+
roots?: string[];
|
|
1261
|
+
/** Roots the project directory does not show, such as a checked-out submodule. Added after the others. */
|
|
1262
|
+
additionalRoots?: string[];
|
|
1263
|
+
/** The packs the extraction ran with. A hop only a pack declares, such as `session.get(User, id)` giving back a User, is explained only when that pack is here. */
|
|
1264
|
+
packs?: readonly PythonPack[];
|
|
1033
1265
|
}
|
|
1034
1266
|
/** A found node, paired with the file it was parsed from. */
|
|
1035
1267
|
interface PythonValueHandle {
|
|
@@ -1054,8 +1286,8 @@ declare class PythonWhySession {
|
|
|
1054
1286
|
findCallee(file: string, startLine: number, endLine: number, calleeText: string): PythonValueHandle | null;
|
|
1055
1287
|
/**
|
|
1056
1288
|
* Why `value` resolves to what it does: the witness proof, flattened
|
|
1057
|
-
* to the chain and rendered. Null when the value does not resolve,
|
|
1058
|
-
*
|
|
1289
|
+
* to the chain and rendered. Null when the value does not resolve, and
|
|
1290
|
+
* the caller then writes its own message.
|
|
1059
1291
|
*/
|
|
1060
1292
|
explain(value: PythonValueHandle, options?: {
|
|
1061
1293
|
maxDepth?: number;
|
|
@@ -1063,23 +1295,18 @@ declare class PythonWhySession {
|
|
|
1063
1295
|
private locate;
|
|
1064
1296
|
private rootOf;
|
|
1065
1297
|
private pathOf;
|
|
1066
|
-
/** A file path
|
|
1298
|
+
/** A file path relative to the root, or unchanged when it is outside the root. */
|
|
1067
1299
|
private displayPath;
|
|
1068
1300
|
}
|
|
1069
1301
|
|
|
1070
1302
|
/**
|
|
1071
|
-
* The
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1303
|
+
* The facts the module resolver adds to the shared store.
|
|
1304
|
+
*
|
|
1305
|
+
* `importsModule` records each module a file imports, as written, and
|
|
1306
|
+
* `importsFile` the file an import resolved to. An import the resolver
|
|
1307
|
+
* abstains on gets no file rather than a guessed one.
|
|
1074
1308
|
*/
|
|
1075
|
-
|
|
1076
|
-
start: number;
|
|
1077
|
-
end: number;
|
|
1078
|
-
}, name: string): string;
|
|
1079
|
-
declare function emitEntryFact(db: Database, filePath: string, range: {
|
|
1080
|
-
start: number;
|
|
1081
|
-
end: number;
|
|
1082
|
-
}, name: string): void;
|
|
1309
|
+
|
|
1083
1310
|
/**
|
|
1084
1311
|
* Every import in the file, wherever it is written. Python code puts an import
|
|
1085
1312
|
* inside a function to break a cycle between two modules, and a chain through
|
|
@@ -1087,17 +1314,74 @@ declare function emitEntryFact(db: Database, filePath: string, range: {
|
|
|
1087
1314
|
*/
|
|
1088
1315
|
declare function emitModuleImportFacts(db: Database, filePath: string, module: ModuleBinding, resolverOptions: ModuleResolverOptions): void;
|
|
1089
1316
|
|
|
1317
|
+
/**
|
|
1318
|
+
* The directories a Python project's absolute imports resolve against:
|
|
1319
|
+
* the project directory, and the source directories it declares.
|
|
1320
|
+
*
|
|
1321
|
+
* A project that keeps its package under `src/` imports it as
|
|
1322
|
+
* `orders.routes`, because installing the project puts `src/` on
|
|
1323
|
+
* `sys.path`. suss cannot run the install, so it reads where
|
|
1324
|
+
* `pyproject.toml` tells the build backend to look, and uses `src/`
|
|
1325
|
+
* when that declares nothing and `src/` contains a package.
|
|
1326
|
+
*
|
|
1327
|
+
* An extra root cannot pick the wrong module, because the module
|
|
1328
|
+
* resolver abstains when two roots both have a file for one import.
|
|
1329
|
+
*/
|
|
1330
|
+
interface UnreadManifest {
|
|
1331
|
+
/** The file, relative to the project root. */
|
|
1332
|
+
where: string;
|
|
1333
|
+
/** Why it could not be read, in one line. */
|
|
1334
|
+
reason: string;
|
|
1335
|
+
}
|
|
1336
|
+
interface PythonSourceRoots {
|
|
1337
|
+
/** Absolute: the project directory first, then each source directory declared or found under it. */
|
|
1338
|
+
roots: string[];
|
|
1339
|
+
/** A manifest that might have declared a source directory and could not be read. */
|
|
1340
|
+
unread: UnreadManifest[];
|
|
1341
|
+
}
|
|
1342
|
+
type TomlTable = Record<string, unknown>;
|
|
1343
|
+
type TomlFileRead = {
|
|
1344
|
+
kind: "parsed";
|
|
1345
|
+
value: unknown;
|
|
1346
|
+
} | {
|
|
1347
|
+
kind: "unreadable";
|
|
1348
|
+
reason: string;
|
|
1349
|
+
};
|
|
1350
|
+
declare function readTomlFile(file: string): TomlFileRead;
|
|
1351
|
+
declare function tableAt(value: unknown, ...keys: string[]): TomlTable | null;
|
|
1352
|
+
declare function pythonSourceRoots(projectRoot: string): PythonSourceRoots;
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* The entry point for a Python run. It discovers units, assembles their
|
|
1356
|
+
* summaries in the shared IR, and emits the run's facts into one `Database`.
|
|
1357
|
+
*
|
|
1358
|
+
* Every file is parsed and bound first, and the value facts are emitted
|
|
1359
|
+
* across all of them, before discovery runs on any file, because a mount in
|
|
1360
|
+
* one file can refer to a router built in another. Each discovered unit
|
|
1361
|
+
* goes through `@suss/extractor`'s `assembleSummary`, the same assembly the
|
|
1362
|
+
* TypeScript adapter uses, so both languages share one gap detection. With
|
|
1363
|
+
* a cache, a run after an edit replays the files and functions whose
|
|
1364
|
+
* inputs did not change, through the records in `reuse.ts`.
|
|
1365
|
+
*/
|
|
1366
|
+
|
|
1090
1367
|
interface ExtractPythonOptions {
|
|
1091
1368
|
/** Absolute paths of the files to parse and extract. */
|
|
1092
1369
|
files: string[];
|
|
1093
1370
|
packs: PythonPack[];
|
|
1094
|
-
/**
|
|
1095
|
-
|
|
1371
|
+
/**
|
|
1372
|
+
* Directories an absolute import is resolved against. When absent,
|
|
1373
|
+
* they are read from `projectRoot`: the directory itself and the
|
|
1374
|
+
* source directories its `pyproject.toml` declares, or `src/` when
|
|
1375
|
+
* it contains a package.
|
|
1376
|
+
*/
|
|
1377
|
+
roots?: string[];
|
|
1378
|
+
/** Roots that cannot be found from the project directory, such as a checked-out submodule. Added after the others. */
|
|
1379
|
+
additionalRoots?: string[];
|
|
1096
1380
|
/** When set, `location.file` on each summary is relativized against this. */
|
|
1097
1381
|
workspaceRoot?: string;
|
|
1098
1382
|
/** The directory a summary's id measures its file from, when that differs from `workspaceRoot`. */
|
|
1099
1383
|
projectRoot?: string;
|
|
1100
|
-
/**
|
|
1384
|
+
/** How much of what could not be read goes on a summary. "strict" also makes a route that cannot be built stop the run. */
|
|
1101
1385
|
gapHandling?: ExtractorOptions["gapHandling"];
|
|
1102
1386
|
/** Called once with the run's per-phase wall time, for `suss extract --timing`. */
|
|
1103
1387
|
onTiming?: (report: TimingReport) => void;
|
|
@@ -1107,10 +1391,31 @@ interface ExtractPythonOptions {
|
|
|
1107
1391
|
onCacheDiagnostic?: (diagnostic: CacheDiagnostic) => void;
|
|
1108
1392
|
/** Absolute. `<projectRoot>/.suss/cache` by default; `null` turns it off. */
|
|
1109
1393
|
cacheDir?: string | null;
|
|
1394
|
+
/** The modules the project lists in `suss.json`, with absolute paths. */
|
|
1395
|
+
modules?: readonly DeclaredModule[];
|
|
1396
|
+
/** Parses an earlier run in this process left, for a file whose text is unchanged. */
|
|
1397
|
+
keptParses?: KeptPythonParses;
|
|
1110
1398
|
}
|
|
1399
|
+
/** A file's tree and the scopes bound over it, as a later run can reuse them. */
|
|
1400
|
+
type KeptPythonParses = KeptParses<{
|
|
1401
|
+
root: Node;
|
|
1402
|
+
module: ModuleBinding;
|
|
1403
|
+
}>;
|
|
1404
|
+
/** A holder for parses between runs, which frees each tree it lets go of. */
|
|
1405
|
+
declare function keptPythonParses(): KeptPythonParses;
|
|
1406
|
+
/**
|
|
1407
|
+
* Parses these files into `kept` before any run needs them. A run served
|
|
1408
|
+
* whole from the cache parses nothing, so a process that keeps parses
|
|
1409
|
+
* calls this while nobody is waiting.
|
|
1410
|
+
*/
|
|
1411
|
+
declare function parsePythonAhead(files: readonly string[], kept: KeptPythonParses): Promise<void>;
|
|
1111
1412
|
interface ExtractPythonResult {
|
|
1112
1413
|
summaries: BehavioralSummary[];
|
|
1113
1414
|
facts: Database;
|
|
1415
|
+
/** The roots absolute imports were resolved against. */
|
|
1416
|
+
roots: string[];
|
|
1417
|
+
/** A manifest that might have declared a source directory and could not be read. */
|
|
1418
|
+
unreadManifests: UnreadManifest[];
|
|
1114
1419
|
}
|
|
1115
1420
|
/** One parsed file and the packs a run over it would load. */
|
|
1116
1421
|
interface FileFactsOptions {
|
|
@@ -1133,19 +1438,19 @@ declare function extractPythonProject(options: ExtractPythonOptions): Promise<Ex
|
|
|
1133
1438
|
declare function findPythonFiles(root: string): string[];
|
|
1134
1439
|
|
|
1135
1440
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
*
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1140
|
-
* stub covers one imported module at a time
|
|
1141
|
-
*
|
|
1441
|
+
* Every import in the project of a module or one of its submodules, for
|
|
1442
|
+
* drafting a stub of a project's wrapper package.
|
|
1443
|
+
*
|
|
1444
|
+
* A discovery pattern matches a decorator's import module exactly, so a
|
|
1445
|
+
* stub covers one imported module at a time. The sites are grouped by
|
|
1446
|
+
* the module text as the project wrote it for that reason.
|
|
1142
1447
|
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1448
|
+
* The walk visits every node instead of asking the binder, so an import
|
|
1449
|
+
* inside an `if` or a `try` still counts here, even though the binder
|
|
1450
|
+
* does not resolve a name through it.
|
|
1146
1451
|
*/
|
|
1147
1452
|
interface PythonImportSite {
|
|
1148
|
-
/** The name imported at this site
|
|
1453
|
+
/** The name imported at this site. Null for a bare `import module`, which binds the whole module. */
|
|
1149
1454
|
name: string | null;
|
|
1150
1455
|
file: string;
|
|
1151
1456
|
line: number;
|
|
@@ -1167,13 +1472,13 @@ interface PythonImportEvidenceOptions {
|
|
|
1167
1472
|
declare function pythonImportEvidence(options: PythonImportEvidenceOptions): Promise<PythonImportEvidence[]>;
|
|
1168
1473
|
|
|
1169
1474
|
/**
|
|
1170
|
-
*
|
|
1475
|
+
* The Python adapter's part of the extraction cache key.
|
|
1171
1476
|
*
|
|
1172
|
-
* `ADAPTER_VERSION`
|
|
1173
|
-
*
|
|
1174
|
-
* terminal
|
|
1175
|
-
*
|
|
1477
|
+
* Bump `ADAPTER_VERSION` by hand on any change to what extraction
|
|
1478
|
+
* produces, such as the IR it writes, what discovery finds, or how a
|
|
1479
|
+
* terminal is classified. Summaries cached under the old version are
|
|
1480
|
+
* then read again instead of reused.
|
|
1176
1481
|
*/
|
|
1177
1482
|
declare const ADAPTER_VERSION = "0.1.0";
|
|
1178
1483
|
|
|
1179
|
-
export { ADAPTER_VERSION, type AnnotationContext, type Binding, type BoundPythonFile, type DecoratedClassRoute, type DecoratedFunctionRoute, type DecoratorArg, type DecoratorClassification, type DiscoveryOptions, type ExtractPythonOptions, type ExtractPythonResult, type FileFactsOptions, type ModuleBinding, type ModuleResolution, type ModuleResolverOptions, type MountObjectCarrier, type MountObjectPrefix, type MountPrefixEffect, type NoValuePrefix, type PathRepeatedSlashes, type PrefixTrailingSlash, type PyModelEntryFunction, type PyModelEntryMethod, type PyModelQueries, type PyStatusCall, type PythonDiscoveryPattern, type PythonImportEvidence, type PythonImportEvidenceOptions, type PythonImportSite, type PythonPack, type PythonValueHandle, PythonWhySession, type PythonWhySessionOptions, type RawSqlPattern, type RelativeModuleSpec, type RouteConventions, type RoutePrefixResolution, type RouterComposition, type RouterIndex, type Scope, type ScopeKind, type SqlCallArgument, type SqlClientHandoff, type SqlClientPattern, type SqlStatementCall, type SqlTableCall, type StoragePattern, annotationToShape, bindModule, buildRouterIndex, classifyDecorator, containedValues, discoverUnits,
|
|
1484
|
+
export { ADAPTER_VERSION, type AnnotationContext, type Binding, type BoundPythonFile, type DecoratedClassRoute, type DecoratedFunctionRoute, type DecoratorArg, type DecoratorClassification, type DiscoveryOptions, type ExtractPythonOptions, type ExtractPythonResult, type FileFactsOptions, type KeptPythonParses, type ModuleBinding, type ModuleResolution, type ModuleResolverOptions, type MountObjectCarrier, type MountObjectPrefix, type MountPrefixEffect, type NoValuePrefix, type ParameterSource, type PathRepeatedSlashes, type PrefixTrailingSlash, type PyModelEntryFunction, type PyModelEntryMethod, type PyModelQueries, type PyStatusCall, type PyTestCases, type PyTestMocks, type PyTestSetUp, type PythonDiscoveryPattern, type PythonImportEvidence, type PythonImportEvidenceOptions, type PythonImportSite, type PythonPack, type PythonSourceRoots, type PythonValueHandle, PythonWhySession, type PythonWhySessionOptions, type RawSqlPattern, type RelativeModuleSpec, type RequestObject, type RouteConventions, type RoutePrefixResolution, type RouterComposition, type RouterIndex, type Scope, type ScopeKind, type SqlCallArgument, type SqlClientHandoff, type SqlClientPattern, type SqlStatementCall, type SqlTableCall, type StoragePattern, type TomlFileRead, type TomlTable, type UnreadManifest, annotationToShape, bindModule, buildRouterIndex, classifyDecorator, containedValues, discoverUnits, emitModuleImportFacts, emitValueFacts, extractPythonProject, factsForFile, findPythonFiles, keptPythonParses, nameKeyIn, nodeId, objectReturnedBy, parsePython, parsePythonAhead, parsePythonSync, preloadPythonGrammar, pythonImportEvidence, pythonSourceRoots, readKey, readTomlFile, resolveAbsoluteModule, resolveCalls, resolveModule, resolveName, resolveRelativeModule, shapeFromName, tableAt };
|