@suss/adapter-python 0.22.4 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
1
+ import { 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 { ExtractorOptions, RawCodeStructure, TimingReport, ExtractionReport, CacheDiagnostic } from '@suss/extractor';
5
+ import { RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic } from '@suss/extractor';
6
6
  import { WhyExplained } from '@suss/resolution';
7
7
 
8
8
  /**
@@ -229,6 +229,8 @@ interface PythonPack {
229
229
  * and never says why (#188).
230
230
  */
231
231
  projectModules?: string[];
232
+ /** The callables the library gives a project for making a request. */
233
+ clients?: PyClientCall[];
232
234
  /** What the library's own database queries look like. The README says how one is matched. */
233
235
  storage?: StoragePattern[];
234
236
  /** How the library lets a project hand the database SQL it wrote itself. */
@@ -285,6 +287,49 @@ interface RawSqlPattern {
285
287
  storageSystem: "postgresql" | "mysql" | "sqlite";
286
288
  }
287
289
  type PythonDiscoveryPattern = DecoratedClassRoute | DecoratedFunctionRoute;
290
+ /**
291
+ * The callables a library gives a project for making a request. A
292
+ * function that calls one of them is a client of the boundary that call
293
+ * states, and gets a unit bound to its method and path.
294
+ */
295
+ interface PyClientCall {
296
+ type: "clientCall";
297
+ /** The module the callables come from, `requests`. */
298
+ importModule: string[];
299
+ /** Attribute names that state the method themselves: `get` means GET. */
300
+ verbAttributeNames: Record<string, string>;
301
+ /** Where a call whose name states the method states its URL. */
302
+ url: {
303
+ position: number;
304
+ keyword: string;
305
+ };
306
+ /** A call that takes the method as an argument instead, `request("GET", url)`. */
307
+ methodCall?: {
308
+ attribute: string;
309
+ methodPosition: number;
310
+ methodKeyword?: string;
311
+ urlPosition: number;
312
+ };
313
+ /** Constructors whose instances take the same calls, `Session`. */
314
+ receiverConstructors?: string[];
315
+ /** What the response object gives a caller, so a guard on it reads as a guard on a status. */
316
+ response?: PyClientResponse;
317
+ }
318
+ /**
319
+ * The members a library's response object gives a caller. A caller that
320
+ * tests one of them is saying which statuses it handles, and the
321
+ * checker compares that against what the other side produces.
322
+ */
323
+ interface PyClientResponse {
324
+ /** Members whose value is the status code: `status_code`. */
325
+ statusCode?: string[];
326
+ /** Members that say the request succeeded, meaning a status in 200 to 299: `ok`. */
327
+ success?: string[];
328
+ /** Members that give the body: `json`, `text`, `content`. */
329
+ body?: string[];
330
+ /** Whether a refused request comes back as a response or raises where it was made. */
331
+ failureDelivery?: "response" | "exception";
332
+ }
288
333
  /** Conventions both kinds of route share. Each one describes what the library does, never a project's choice. */
289
334
  interface RouteConventions {
290
335
  /** How the library spells a path parameter. The README lists the syntaxes we know how to read. */
@@ -323,8 +368,74 @@ interface RouteConventions {
323
368
  * and one it does cover comes out as the response the library sends.
324
369
  */
325
370
  responseStatusCalls?: PyStatusCall[];
371
+ /**
372
+ * The library's own classes whose instance, when a body returns it, is
373
+ * the response the library sends, and where each takes the status:
374
+ * Starlette's `JSONResponse(status_code=...)`. A return of one says its
375
+ * own status; a return of anything else keeps the declared one.
376
+ */
377
+ responseConstructors?: PyStatusCall[];
326
378
  /** Unset means the library has no router mounting, and a route's decorator path stands as written. */
327
379
  routerComposition?: RouterComposition;
380
+ /** The ways the library runs a project's own function around a route. The README lists what each one covers. */
381
+ wrappers?: PyWrapperForm[];
382
+ }
383
+ type PyWrapperForm = PyDependencyForm | PyDecoratedWrapperForm;
384
+ /** Where a wrapper is registered, and which routes the registration reaches. */
385
+ interface PyWrapperRegistrar {
386
+ /** The constructor of the object the registration is written on, as the library exports it: `FastAPI`, `APIRouter`. */
387
+ constructorName: string;
388
+ /** Where the constructor is imported from, when that is not the pattern's own `importModule`: `flask` for the app a flask-restx API is served by. */
389
+ importModule?: string[];
390
+ /**
391
+ * `everyRoute` for the app, whose registration reaches every route of
392
+ * the pack in the run. `ownRoutes` for a router or a blueprint, whose
393
+ * registration reaches the routes decorated on that same object.
394
+ */
395
+ covers: "everyRoute" | "ownRoutes";
396
+ }
397
+ /**
398
+ * The library calls a project function before the handler, given as an
399
+ * argument to one of its own callables: FastAPI's `Depends(get_user)`.
400
+ * One may be written as a parameter default or inside `Annotated[...]`
401
+ * on the route, or in a list under `keyword` on the route decorator or
402
+ * on one of the registrars. The function runs before the handler and
403
+ * ends the request by raising, so it is a wrapper whose every return
404
+ * hands on.
405
+ */
406
+ interface PyDependencyForm {
407
+ type: "dependency";
408
+ /** The callables that take the function: `Depends`, `Security`. */
409
+ callees: string[];
410
+ /** The keyword a list of them is written under: `dependencies`. */
411
+ keyword: string;
412
+ registrars: PyWrapperRegistrar[];
413
+ }
414
+ /**
415
+ * A project function decorated with a method on the app or a router:
416
+ * `@app.middleware("http")`, `@app.exception_handler(ValueError)`,
417
+ * `@app.before_request`. What the decorated function's returns mean
418
+ * depends on which of the three fields below is set; with none set,
419
+ * every return hands the request on.
420
+ */
421
+ interface PyDecoratedWrapperForm {
422
+ type: "decoratedWrapper";
423
+ /** The decorator's attribute name on the registrar: `middleware`. */
424
+ attribute: string;
425
+ registrars: PyWrapperRegistrar[];
426
+ /**
427
+ * The position of the parameter the wrapper calls to run what it
428
+ * wraps, `call_next` at 1 for Starlette middleware. A return before
429
+ * that call ends the request with what is returned.
430
+ */
431
+ continuationParam?: number;
432
+ /**
433
+ * Set when a value returned ends the request and only a bare return
434
+ * hands on, which is what Flask does with `before_request`.
435
+ */
436
+ returnedValueResponds?: boolean;
437
+ /** The position the library hands the raised exception at. Set on an error handler, which runs only when the handler raised. */
438
+ throwParam?: number;
328
439
  }
329
440
  /**
330
441
  * One callable that ends the request, and where it takes the status.
@@ -577,6 +688,98 @@ interface RouterIndexOptions extends ModuleResolverOptions {
577
688
  }
578
689
  declare function buildRouterIndex(files: BoundPythonFile[], packs: PythonPack[], resolverOptions: RouterIndexOptions): RouterIndex;
579
690
 
691
+ /**
692
+ * What a call's callee is, read through the binder and the import
693
+ * facts: a function in this run the walk can step into, or a reason it
694
+ * cannot. A name is followed only through a binding that says where it
695
+ * came from, so a name that could be two definitions is a stop rather
696
+ * than a guess. The package README lists the spellings that are
697
+ * followed and the ones that stop.
698
+ */
699
+
700
+ /** A function in this run, and the export path its summary gets. */
701
+ interface ReachedFunction {
702
+ readonly file: BoundPythonFile;
703
+ /** The `function_definition` node. */
704
+ readonly node: Node;
705
+ readonly name: string;
706
+ /** `[name]` for a module function, `[Class, name]` for a method. */
707
+ readonly exportPath: string[];
708
+ }
709
+
710
+ /**
711
+ * The project functions a library runs around a route, read from where
712
+ * they are registered: a dependency on a route's parameter or decorator,
713
+ * a dependency list on the app or a router, a function decorated with
714
+ * `@app.middleware(...)` or `@app.exception_handler(...)`. Each one
715
+ * becomes a unit of its own, and every route it reaches lists it, so the
716
+ * extractor can fold its outcomes into the route's. The README says which
717
+ * registrations are read and which are not.
718
+ *
719
+ * A registration on the app reaches every route of the pack in the run,
720
+ * since an app is one per run for every pack that has one. A registration
721
+ * on a router reaches the routes decorated on that same router object,
722
+ * and nothing mounted onto it.
723
+ */
724
+
725
+ interface WrapperIndexOptions {
726
+ packs: readonly PythonPack[];
727
+ roots: string[];
728
+ facts: Database | undefined;
729
+ storageFor: (file: BoundPythonFile) => StorageLookup | undefined;
730
+ }
731
+ /** The route asking which wrappers reach it: the decorator it was found by, and where that decorator was read. */
732
+ interface RouteWrapperQuery {
733
+ pack: PythonPack;
734
+ pattern: PythonDiscoveryPattern;
735
+ /** The absolute path of the file the route is written in. */
736
+ file: string;
737
+ module: ModuleBinding;
738
+ classification: DecoratorClassification;
739
+ definitionNode: Node;
740
+ }
741
+ /** A wrapper the index has a unit for, and the form it was registered through. */
742
+ interface Registered {
743
+ reference: WrapperReference;
744
+ form: PyWrapperForm;
745
+ }
746
+ interface FormOf {
747
+ pack: PythonPack;
748
+ pattern: PythonDiscoveryPattern;
749
+ form: PyWrapperForm;
750
+ }
751
+ /** What every registration in the run reaches, asked once per route and then once per file for the wrapper units. */
752
+ declare class PythonWrapperIndex {
753
+ readonly options: WrapperIndexOptions;
754
+ readonly filesByPath: ReadonlyMap<string, BoundPythonFile>;
755
+ /** By pack name: what the app registered, in the order the forms are declared and then the order the files were read. */
756
+ private readonly everyRoute;
757
+ /** By construction key: what a router or a blueprint registered. */
758
+ private readonly ownRoutes;
759
+ private readonly unitsByKey;
760
+ private readonly unitsByFile;
761
+ constructor(options: WrapperIndexOptions, filesByPath: ReadonlyMap<string, BoundPythonFile>);
762
+ register(covers: {
763
+ kind: "everyRoute";
764
+ pack: string;
765
+ } | {
766
+ kind: "ownRoutes";
767
+ key: string;
768
+ }, target: ReachedFunction, declared: FormOf): void;
769
+ /** The unit for a function, built once however many registrations point at it. */
770
+ registered(target: ReachedFunction, declared: FormOf): Registered;
771
+ wrappersFor(query: RouteWrapperQuery): WrapperReference[];
772
+ private ownRoutesOf;
773
+ /**
774
+ * Where the decorator's object was built: the route's own file, the file
775
+ * a project wrapper function is written in, or the file an imported
776
+ * router comes from. The name is the one that file binds it under.
777
+ */
778
+ private objectSiteOf;
779
+ private fileOfModule;
780
+ unitsIn(file: string): RawCodeStructure[];
781
+ }
782
+
580
783
  /**
581
784
  * Finds decorated routes and turns each one into a `RawCodeStructure`.
582
785
  *
@@ -605,6 +808,8 @@ interface DiscoveryOptions {
605
808
  absoluteFile?: string | undefined;
606
809
  /** The project's facts, so the rules can say what an object no scope has a binding for was built by. */
607
810
  facts?: Database | undefined;
811
+ /** What the project registered around its routes. Absent when no pack declares a wrapper form, or when a caller reads one file on its own. */
812
+ wrappers?: PythonWrapperIndex | undefined;
608
813
  }
609
814
  declare function discoverUnits(root: Node, module: ModuleBinding, options: DiscoveryOptions): RawCodeStructure[];
610
815
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { TypeShape, BehavioralSummary } from '@suss/behavioral-ir';
1
+ import { 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 { ExtractorOptions, RawCodeStructure, TimingReport, ExtractionReport, CacheDiagnostic } from '@suss/extractor';
5
+ import { RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic } from '@suss/extractor';
6
6
  import { WhyExplained } from '@suss/resolution';
7
7
 
8
8
  /**
@@ -229,6 +229,8 @@ interface PythonPack {
229
229
  * and never says why (#188).
230
230
  */
231
231
  projectModules?: string[];
232
+ /** The callables the library gives a project for making a request. */
233
+ clients?: PyClientCall[];
232
234
  /** What the library's own database queries look like. The README says how one is matched. */
233
235
  storage?: StoragePattern[];
234
236
  /** How the library lets a project hand the database SQL it wrote itself. */
@@ -285,6 +287,49 @@ interface RawSqlPattern {
285
287
  storageSystem: "postgresql" | "mysql" | "sqlite";
286
288
  }
287
289
  type PythonDiscoveryPattern = DecoratedClassRoute | DecoratedFunctionRoute;
290
+ /**
291
+ * The callables a library gives a project for making a request. A
292
+ * function that calls one of them is a client of the boundary that call
293
+ * states, and gets a unit bound to its method and path.
294
+ */
295
+ interface PyClientCall {
296
+ type: "clientCall";
297
+ /** The module the callables come from, `requests`. */
298
+ importModule: string[];
299
+ /** Attribute names that state the method themselves: `get` means GET. */
300
+ verbAttributeNames: Record<string, string>;
301
+ /** Where a call whose name states the method states its URL. */
302
+ url: {
303
+ position: number;
304
+ keyword: string;
305
+ };
306
+ /** A call that takes the method as an argument instead, `request("GET", url)`. */
307
+ methodCall?: {
308
+ attribute: string;
309
+ methodPosition: number;
310
+ methodKeyword?: string;
311
+ urlPosition: number;
312
+ };
313
+ /** Constructors whose instances take the same calls, `Session`. */
314
+ receiverConstructors?: string[];
315
+ /** What the response object gives a caller, so a guard on it reads as a guard on a status. */
316
+ response?: PyClientResponse;
317
+ }
318
+ /**
319
+ * The members a library's response object gives a caller. A caller that
320
+ * tests one of them is saying which statuses it handles, and the
321
+ * checker compares that against what the other side produces.
322
+ */
323
+ interface PyClientResponse {
324
+ /** Members whose value is the status code: `status_code`. */
325
+ statusCode?: string[];
326
+ /** Members that say the request succeeded, meaning a status in 200 to 299: `ok`. */
327
+ success?: string[];
328
+ /** Members that give the body: `json`, `text`, `content`. */
329
+ body?: string[];
330
+ /** Whether a refused request comes back as a response or raises where it was made. */
331
+ failureDelivery?: "response" | "exception";
332
+ }
288
333
  /** Conventions both kinds of route share. Each one describes what the library does, never a project's choice. */
289
334
  interface RouteConventions {
290
335
  /** How the library spells a path parameter. The README lists the syntaxes we know how to read. */
@@ -323,8 +368,74 @@ interface RouteConventions {
323
368
  * and one it does cover comes out as the response the library sends.
324
369
  */
325
370
  responseStatusCalls?: PyStatusCall[];
371
+ /**
372
+ * The library's own classes whose instance, when a body returns it, is
373
+ * the response the library sends, and where each takes the status:
374
+ * Starlette's `JSONResponse(status_code=...)`. A return of one says its
375
+ * own status; a return of anything else keeps the declared one.
376
+ */
377
+ responseConstructors?: PyStatusCall[];
326
378
  /** Unset means the library has no router mounting, and a route's decorator path stands as written. */
327
379
  routerComposition?: RouterComposition;
380
+ /** The ways the library runs a project's own function around a route. The README lists what each one covers. */
381
+ wrappers?: PyWrapperForm[];
382
+ }
383
+ type PyWrapperForm = PyDependencyForm | PyDecoratedWrapperForm;
384
+ /** Where a wrapper is registered, and which routes the registration reaches. */
385
+ interface PyWrapperRegistrar {
386
+ /** The constructor of the object the registration is written on, as the library exports it: `FastAPI`, `APIRouter`. */
387
+ constructorName: string;
388
+ /** Where the constructor is imported from, when that is not the pattern's own `importModule`: `flask` for the app a flask-restx API is served by. */
389
+ importModule?: string[];
390
+ /**
391
+ * `everyRoute` for the app, whose registration reaches every route of
392
+ * the pack in the run. `ownRoutes` for a router or a blueprint, whose
393
+ * registration reaches the routes decorated on that same object.
394
+ */
395
+ covers: "everyRoute" | "ownRoutes";
396
+ }
397
+ /**
398
+ * The library calls a project function before the handler, given as an
399
+ * argument to one of its own callables: FastAPI's `Depends(get_user)`.
400
+ * One may be written as a parameter default or inside `Annotated[...]`
401
+ * on the route, or in a list under `keyword` on the route decorator or
402
+ * on one of the registrars. The function runs before the handler and
403
+ * ends the request by raising, so it is a wrapper whose every return
404
+ * hands on.
405
+ */
406
+ interface PyDependencyForm {
407
+ type: "dependency";
408
+ /** The callables that take the function: `Depends`, `Security`. */
409
+ callees: string[];
410
+ /** The keyword a list of them is written under: `dependencies`. */
411
+ keyword: string;
412
+ registrars: PyWrapperRegistrar[];
413
+ }
414
+ /**
415
+ * A project function decorated with a method on the app or a router:
416
+ * `@app.middleware("http")`, `@app.exception_handler(ValueError)`,
417
+ * `@app.before_request`. What the decorated function's returns mean
418
+ * depends on which of the three fields below is set; with none set,
419
+ * every return hands the request on.
420
+ */
421
+ interface PyDecoratedWrapperForm {
422
+ type: "decoratedWrapper";
423
+ /** The decorator's attribute name on the registrar: `middleware`. */
424
+ attribute: string;
425
+ registrars: PyWrapperRegistrar[];
426
+ /**
427
+ * The position of the parameter the wrapper calls to run what it
428
+ * wraps, `call_next` at 1 for Starlette middleware. A return before
429
+ * that call ends the request with what is returned.
430
+ */
431
+ continuationParam?: number;
432
+ /**
433
+ * Set when a value returned ends the request and only a bare return
434
+ * hands on, which is what Flask does with `before_request`.
435
+ */
436
+ returnedValueResponds?: boolean;
437
+ /** The position the library hands the raised exception at. Set on an error handler, which runs only when the handler raised. */
438
+ throwParam?: number;
328
439
  }
329
440
  /**
330
441
  * One callable that ends the request, and where it takes the status.
@@ -577,6 +688,98 @@ interface RouterIndexOptions extends ModuleResolverOptions {
577
688
  }
578
689
  declare function buildRouterIndex(files: BoundPythonFile[], packs: PythonPack[], resolverOptions: RouterIndexOptions): RouterIndex;
579
690
 
691
+ /**
692
+ * What a call's callee is, read through the binder and the import
693
+ * facts: a function in this run the walk can step into, or a reason it
694
+ * cannot. A name is followed only through a binding that says where it
695
+ * came from, so a name that could be two definitions is a stop rather
696
+ * than a guess. The package README lists the spellings that are
697
+ * followed and the ones that stop.
698
+ */
699
+
700
+ /** A function in this run, and the export path its summary gets. */
701
+ interface ReachedFunction {
702
+ readonly file: BoundPythonFile;
703
+ /** The `function_definition` node. */
704
+ readonly node: Node;
705
+ readonly name: string;
706
+ /** `[name]` for a module function, `[Class, name]` for a method. */
707
+ readonly exportPath: string[];
708
+ }
709
+
710
+ /**
711
+ * The project functions a library runs around a route, read from where
712
+ * they are registered: a dependency on a route's parameter or decorator,
713
+ * a dependency list on the app or a router, a function decorated with
714
+ * `@app.middleware(...)` or `@app.exception_handler(...)`. Each one
715
+ * becomes a unit of its own, and every route it reaches lists it, so the
716
+ * extractor can fold its outcomes into the route's. The README says which
717
+ * registrations are read and which are not.
718
+ *
719
+ * A registration on the app reaches every route of the pack in the run,
720
+ * since an app is one per run for every pack that has one. A registration
721
+ * on a router reaches the routes decorated on that same router object,
722
+ * and nothing mounted onto it.
723
+ */
724
+
725
+ interface WrapperIndexOptions {
726
+ packs: readonly PythonPack[];
727
+ roots: string[];
728
+ facts: Database | undefined;
729
+ storageFor: (file: BoundPythonFile) => StorageLookup | undefined;
730
+ }
731
+ /** The route asking which wrappers reach it: the decorator it was found by, and where that decorator was read. */
732
+ interface RouteWrapperQuery {
733
+ pack: PythonPack;
734
+ pattern: PythonDiscoveryPattern;
735
+ /** The absolute path of the file the route is written in. */
736
+ file: string;
737
+ module: ModuleBinding;
738
+ classification: DecoratorClassification;
739
+ definitionNode: Node;
740
+ }
741
+ /** A wrapper the index has a unit for, and the form it was registered through. */
742
+ interface Registered {
743
+ reference: WrapperReference;
744
+ form: PyWrapperForm;
745
+ }
746
+ interface FormOf {
747
+ pack: PythonPack;
748
+ pattern: PythonDiscoveryPattern;
749
+ form: PyWrapperForm;
750
+ }
751
+ /** What every registration in the run reaches, asked once per route and then once per file for the wrapper units. */
752
+ declare class PythonWrapperIndex {
753
+ readonly options: WrapperIndexOptions;
754
+ readonly filesByPath: ReadonlyMap<string, BoundPythonFile>;
755
+ /** By pack name: what the app registered, in the order the forms are declared and then the order the files were read. */
756
+ private readonly everyRoute;
757
+ /** By construction key: what a router or a blueprint registered. */
758
+ private readonly ownRoutes;
759
+ private readonly unitsByKey;
760
+ private readonly unitsByFile;
761
+ constructor(options: WrapperIndexOptions, filesByPath: ReadonlyMap<string, BoundPythonFile>);
762
+ register(covers: {
763
+ kind: "everyRoute";
764
+ pack: string;
765
+ } | {
766
+ kind: "ownRoutes";
767
+ key: string;
768
+ }, target: ReachedFunction, declared: FormOf): void;
769
+ /** The unit for a function, built once however many registrations point at it. */
770
+ registered(target: ReachedFunction, declared: FormOf): Registered;
771
+ wrappersFor(query: RouteWrapperQuery): WrapperReference[];
772
+ private ownRoutesOf;
773
+ /**
774
+ * Where the decorator's object was built: the route's own file, the file
775
+ * a project wrapper function is written in, or the file an imported
776
+ * router comes from. The name is the one that file binds it under.
777
+ */
778
+ private objectSiteOf;
779
+ private fileOfModule;
780
+ unitsIn(file: string): RawCodeStructure[];
781
+ }
782
+
580
783
  /**
581
784
  * Finds decorated routes and turns each one into a `RawCodeStructure`.
582
785
  *
@@ -605,6 +808,8 @@ interface DiscoveryOptions {
605
808
  absoluteFile?: string | undefined;
606
809
  /** The project's facts, so the rules can say what an object no scope has a binding for was built by. */
607
810
  facts?: Database | undefined;
811
+ /** What the project registered around its routes. Absent when no pack declares a wrapper form, or when a caller reads one file on its own. */
812
+ wrappers?: PythonWrapperIndex | undefined;
608
813
  }
609
814
  declare function discoverUnits(root: Node, module: ModuleBinding, options: DiscoveryOptions): RawCodeStructure[];
610
815