@suss/adapter-python 0.34.0 → 0.35.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
@@ -2,8 +2,8 @@ import { RequestSpellingMetadata, TypeShape, WrapperReference, BehavioralSummary
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 { RedirectOption, RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic, DeclaredModule, KeptParses } from '@suss/extractor';
5
6
  import { UnwrapsByName, WhyExplained } from '@suss/resolution';
6
- import { RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic, DeclaredModule, KeptParses } from '@suss/extractor';
7
7
 
8
8
  /**
9
9
  * Loads the Python grammar into web-tree-sitter and parses a file.
@@ -415,6 +415,14 @@ interface PyClientResponse {
415
415
  body?: string[];
416
416
  /** Whether a refused request comes back as a response or raises where it was made. */
417
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;
418
426
  }
419
427
  /** An object a module exports that a route reads the request off. */
420
428
  interface RequestObject {
@@ -435,6 +443,13 @@ interface RouteConventions {
435
443
  * annotation says.
436
444
  */
437
445
  injectedParameterCallees?: string[];
446
+ /**
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[];
438
453
  /**
439
454
  * What the library serves when a composed path has repeated slashes.
440
455
  * Werkzeug serves the merged path and redirects the written one, so Flask
@@ -476,6 +491,18 @@ interface RouteConventions {
476
491
  parameterAliasKeyword?: string;
477
492
  /** The ways the library runs a project's own function around a route. DESIGN.md lists what each one covers. */
478
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>;
479
506
  }
480
507
  /** The part of the request a parameter declared with one callable is read from. */
481
508
  interface ParameterSource {
@@ -582,6 +609,13 @@ interface DecoratedFunctionRoute extends RouteConventions {
582
609
  /** Unset leaves the return annotation as the only source for the response shape. */
583
610
  responseModelKeyword?: string;
584
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;
585
619
  }
586
620
  /**
587
621
  * The names a library gives the parts of router mounting, so a route's
@@ -805,6 +839,19 @@ interface RouterIndex {
805
839
  * built it, and every construction is already keyed by its call.
806
840
  */
807
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;
808
855
  }
809
856
  interface RouterIndexOptions extends ModuleResolverOptions {
810
857
  /** The project's facts, so a loop over a call can be settled by the rules. */
@@ -999,8 +1046,8 @@ interface ReachedFunction {
999
1046
  *
1000
1047
  * A registration on the app reaches every route of the pack in the run,
1001
1048
  * because the run treats a pack's app as the only one. A registration on
1002
- * a router reaches the routes decorated on that same router object, and
1003
- * nothing mounted onto it.
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.
1004
1051
  */
1005
1052
 
1006
1053
  interface WrapperIndexOptions {
@@ -1010,6 +1057,8 @@ interface WrapperIndexOptions {
1010
1057
  /** The function each function key was read from. */
1011
1058
  definitions: ReadonlyMap<string, Node>;
1012
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;
1013
1062
  }
1014
1063
  /** The route asking which wrappers reach it: the decorator it was found by, and where that decorator was read. */
1015
1064
  interface RouteWrapperQuery {
@@ -1026,6 +1075,16 @@ interface Registered {
1026
1075
  reference: WrapperReference;
1027
1076
  form: PyWrapperForm;
1028
1077
  }
1078
+ /**
1079
+ * The closure a project's decorator hands back, read with the route's
1080
+ * pattern. It runs the route's function where it calls the decorator's
1081
+ * parameter, `continuation`. No pack declares it, so it has no form.
1082
+ */
1083
+ interface DecoratorClosure {
1084
+ pack: PythonPack;
1085
+ pattern: PythonDiscoveryPattern;
1086
+ continuation: string;
1087
+ }
1029
1088
  /** A wrapper form with the pattern and pack that declare it. */
1030
1089
  interface FormOf {
1031
1090
  pack: PythonPack;
@@ -1041,9 +1100,16 @@ declare class PythonWrapperIndex {
1041
1100
  /** By construction key: what a router or a blueprint registered. */
1042
1101
  private readonly ownRoutes;
1043
1102
  private readonly unitsByKey;
1103
+ /** By pack name: the app constructions its routes run under. */
1104
+ private readonly appCalls;
1105
+ /** By wrapper reference: the parameters a dependency function declares, and its file. */
1106
+ private readonly dependencyParameterTable;
1044
1107
  private readonly unitsByFile;
1045
- /** Told about every registration, so a cache can repeat the ones a file's discovery made. */
1046
- onRegistered: ((target: ReachedFunction, declared: FormOf) => void) | null;
1108
+ /**
1109
+ * Told about every registration, a pack's form or a project decorator's
1110
+ * closure, so a cache can repeat the ones a file's discovery made.
1111
+ */
1112
+ onRegistered: ((target: ReachedFunction, how: FormOf | DecoratorClosure) => void) | null;
1047
1113
  constructor(options: WrapperIndexOptions, filesByPath: ReadonlyMap<string, BoundPythonFile>);
1048
1114
  /**
1049
1115
  * The same lookup `wrappersFor` noted under `id`, made again over this
@@ -1062,8 +1128,62 @@ declare class PythonWrapperIndex {
1062
1128
  }, target: ReachedFunction, declared: FormOf): void;
1063
1129
  /** The unit for a function, built once however many registrations point at it. */
1064
1130
  registered(target: ReachedFunction, declared: FormOf): Registered;
1131
+ /**
1132
+ * The unit for the closure a project's decorator hands back, built once
1133
+ * however many routes the decorator is written on. Its name is what
1134
+ * calling the decorator writes, since the decorator may have a summary
1135
+ * of its own under its name.
1136
+ */
1137
+ registeredClosure(target: ReachedFunction, closure: DecoratorClosure): WrapperReference;
1138
+ /**
1139
+ * The parameters the dependencies among these wrappers declare, and
1140
+ * whether one is declared through a parameter source such as `Query()`.
1141
+ * The library fills those from the request and validates them, the same
1142
+ * as parameters the route declares.
1143
+ */
1144
+ dependencyParameters(references: readonly WrapperReference[]): {
1145
+ names: ReadonlySet<string>;
1146
+ declaresSource: boolean;
1147
+ };
1065
1148
  wrappersFor(query: RouteWrapperQuery): WrapperReference[];
1149
+ /**
1150
+ * The decorators written on the route's function below the one that
1151
+ * registers it, outermost first. Each wraps the function the library
1152
+ * calls. None has a summary: a library's has no body in the run, and a
1153
+ * project's own is not composed into the route yet, so a project
1154
+ * decorator goes by what calling it writes, `require_admin(fn)`, which
1155
+ * no summary is named. The decorators the packs read, such as the
1156
+ * route's own and the namespace's `@ns.doc`, are left out.
1157
+ */
1158
+ private decoratorLayers;
1159
+ private decoratorLayer;
1160
+ /**
1161
+ * The layer a project's own decorator puts around the route. When it
1162
+ * hands back a closure written inside it that returns what calling the
1163
+ * function it was given returns, the closure is summarized and
1164
+ * composed into the route. Any other decorator, including one whose
1165
+ * closure keeps going after that call, goes by the same name with no
1166
+ * summary behind it.
1167
+ */
1168
+ private projectDecoratorLayer;
1169
+ /**
1170
+ * What the mounts above the route's router register on the way down:
1171
+ * the dependencies an `include_router` call lists, and those of each
1172
+ * router it is mounted onto, outermost first, the order they run in.
1173
+ */
1174
+ private mountedAbove;
1175
+ private registeredAbove;
1066
1176
  private ownRoutesOf;
1177
+ /** The construction call of the router a route is decorated on, when a registrar reads it. */
1178
+ private ownRouterOf;
1179
+ /** Records the app a pack's routes run under, for the keywords written on its construction. */
1180
+ recordApp(pack: string, call: Node, file: string): void;
1181
+ /**
1182
+ * Every call that registers something for a route on the way to it:
1183
+ * the app's construction, the mount calls and the routers they mount
1184
+ * onto, and the route's own router. A caller reads a keyword off each.
1185
+ */
1186
+ callsAbove(query: RouteWrapperQuery): Node[];
1067
1187
  /**
1068
1188
  * Where the decorator's object was built: the route's own file, the file
1069
1189
  * a project wrapper function is written in, or the file an imported
@@ -1309,7 +1429,7 @@ interface ExtractPythonOptions {
1309
1429
  onExtractionReport?: (report: ExtractionReport) => void;
1310
1430
  /** Called once with what the cache decided, for `suss extract --timing`. */
1311
1431
  onCacheDiagnostic?: (diagnostic: CacheDiagnostic) => void;
1312
- /** Absolute. `<projectRoot>/.suss/cache` by default; `null` turns it off. */
1432
+ /** Absolute. `<projectRoot>/.suss/cache` by default, or under `SUSS_CACHE_DIR`; `null` turns it off. */
1313
1433
  cacheDir?: string | null;
1314
1434
  /** The modules the project lists in `suss.json`, with absolute paths. */
1315
1435
  modules?: readonly DeclaredModule[];
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { RequestSpellingMetadata, TypeShape, WrapperReference, BehavioralSummary
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 { RedirectOption, RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic, DeclaredModule, KeptParses } from '@suss/extractor';
5
6
  import { UnwrapsByName, WhyExplained } from '@suss/resolution';
6
- import { RawCodeStructure, ExtractorOptions, TimingReport, ExtractionReport, CacheDiagnostic, DeclaredModule, KeptParses } from '@suss/extractor';
7
7
 
8
8
  /**
9
9
  * Loads the Python grammar into web-tree-sitter and parses a file.
@@ -415,6 +415,14 @@ interface PyClientResponse {
415
415
  body?: string[];
416
416
  /** Whether a refused request comes back as a response or raises where it was made. */
417
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;
418
426
  }
419
427
  /** An object a module exports that a route reads the request off. */
420
428
  interface RequestObject {
@@ -435,6 +443,13 @@ interface RouteConventions {
435
443
  * annotation says.
436
444
  */
437
445
  injectedParameterCallees?: string[];
446
+ /**
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[];
438
453
  /**
439
454
  * What the library serves when a composed path has repeated slashes.
440
455
  * Werkzeug serves the merged path and redirects the written one, so Flask
@@ -476,6 +491,18 @@ interface RouteConventions {
476
491
  parameterAliasKeyword?: string;
477
492
  /** The ways the library runs a project's own function around a route. DESIGN.md lists what each one covers. */
478
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>;
479
506
  }
480
507
  /** The part of the request a parameter declared with one callable is read from. */
481
508
  interface ParameterSource {
@@ -582,6 +609,13 @@ interface DecoratedFunctionRoute extends RouteConventions {
582
609
  /** Unset leaves the return annotation as the only source for the response shape. */
583
610
  responseModelKeyword?: string;
584
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;
585
619
  }
586
620
  /**
587
621
  * The names a library gives the parts of router mounting, so a route's
@@ -805,6 +839,19 @@ interface RouterIndex {
805
839
  * built it, and every construction is already keyed by its call.
806
840
  */
807
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;
808
855
  }
809
856
  interface RouterIndexOptions extends ModuleResolverOptions {
810
857
  /** The project's facts, so a loop over a call can be settled by the rules. */
@@ -999,8 +1046,8 @@ interface ReachedFunction {
999
1046
  *
1000
1047
  * A registration on the app reaches every route of the pack in the run,
1001
1048
  * because the run treats a pack's app as the only one. A registration on
1002
- * a router reaches the routes decorated on that same router object, and
1003
- * nothing mounted onto it.
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.
1004
1051
  */
1005
1052
 
1006
1053
  interface WrapperIndexOptions {
@@ -1010,6 +1057,8 @@ interface WrapperIndexOptions {
1010
1057
  /** The function each function key was read from. */
1011
1058
  definitions: ReadonlyMap<string, Node>;
1012
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;
1013
1062
  }
1014
1063
  /** The route asking which wrappers reach it: the decorator it was found by, and where that decorator was read. */
1015
1064
  interface RouteWrapperQuery {
@@ -1026,6 +1075,16 @@ interface Registered {
1026
1075
  reference: WrapperReference;
1027
1076
  form: PyWrapperForm;
1028
1077
  }
1078
+ /**
1079
+ * The closure a project's decorator hands back, read with the route's
1080
+ * pattern. It runs the route's function where it calls the decorator's
1081
+ * parameter, `continuation`. No pack declares it, so it has no form.
1082
+ */
1083
+ interface DecoratorClosure {
1084
+ pack: PythonPack;
1085
+ pattern: PythonDiscoveryPattern;
1086
+ continuation: string;
1087
+ }
1029
1088
  /** A wrapper form with the pattern and pack that declare it. */
1030
1089
  interface FormOf {
1031
1090
  pack: PythonPack;
@@ -1041,9 +1100,16 @@ declare class PythonWrapperIndex {
1041
1100
  /** By construction key: what a router or a blueprint registered. */
1042
1101
  private readonly ownRoutes;
1043
1102
  private readonly unitsByKey;
1103
+ /** By pack name: the app constructions its routes run under. */
1104
+ private readonly appCalls;
1105
+ /** By wrapper reference: the parameters a dependency function declares, and its file. */
1106
+ private readonly dependencyParameterTable;
1044
1107
  private readonly unitsByFile;
1045
- /** Told about every registration, so a cache can repeat the ones a file's discovery made. */
1046
- onRegistered: ((target: ReachedFunction, declared: FormOf) => void) | null;
1108
+ /**
1109
+ * Told about every registration, a pack's form or a project decorator's
1110
+ * closure, so a cache can repeat the ones a file's discovery made.
1111
+ */
1112
+ onRegistered: ((target: ReachedFunction, how: FormOf | DecoratorClosure) => void) | null;
1047
1113
  constructor(options: WrapperIndexOptions, filesByPath: ReadonlyMap<string, BoundPythonFile>);
1048
1114
  /**
1049
1115
  * The same lookup `wrappersFor` noted under `id`, made again over this
@@ -1062,8 +1128,62 @@ declare class PythonWrapperIndex {
1062
1128
  }, target: ReachedFunction, declared: FormOf): void;
1063
1129
  /** The unit for a function, built once however many registrations point at it. */
1064
1130
  registered(target: ReachedFunction, declared: FormOf): Registered;
1131
+ /**
1132
+ * The unit for the closure a project's decorator hands back, built once
1133
+ * however many routes the decorator is written on. Its name is what
1134
+ * calling the decorator writes, since the decorator may have a summary
1135
+ * of its own under its name.
1136
+ */
1137
+ registeredClosure(target: ReachedFunction, closure: DecoratorClosure): WrapperReference;
1138
+ /**
1139
+ * The parameters the dependencies among these wrappers declare, and
1140
+ * whether one is declared through a parameter source such as `Query()`.
1141
+ * The library fills those from the request and validates them, the same
1142
+ * as parameters the route declares.
1143
+ */
1144
+ dependencyParameters(references: readonly WrapperReference[]): {
1145
+ names: ReadonlySet<string>;
1146
+ declaresSource: boolean;
1147
+ };
1065
1148
  wrappersFor(query: RouteWrapperQuery): WrapperReference[];
1149
+ /**
1150
+ * The decorators written on the route's function below the one that
1151
+ * registers it, outermost first. Each wraps the function the library
1152
+ * calls. None has a summary: a library's has no body in the run, and a
1153
+ * project's own is not composed into the route yet, so a project
1154
+ * decorator goes by what calling it writes, `require_admin(fn)`, which
1155
+ * no summary is named. The decorators the packs read, such as the
1156
+ * route's own and the namespace's `@ns.doc`, are left out.
1157
+ */
1158
+ private decoratorLayers;
1159
+ private decoratorLayer;
1160
+ /**
1161
+ * The layer a project's own decorator puts around the route. When it
1162
+ * hands back a closure written inside it that returns what calling the
1163
+ * function it was given returns, the closure is summarized and
1164
+ * composed into the route. Any other decorator, including one whose
1165
+ * closure keeps going after that call, goes by the same name with no
1166
+ * summary behind it.
1167
+ */
1168
+ private projectDecoratorLayer;
1169
+ /**
1170
+ * What the mounts above the route's router register on the way down:
1171
+ * the dependencies an `include_router` call lists, and those of each
1172
+ * router it is mounted onto, outermost first, the order they run in.
1173
+ */
1174
+ private mountedAbove;
1175
+ private registeredAbove;
1066
1176
  private ownRoutesOf;
1177
+ /** The construction call of the router a route is decorated on, when a registrar reads it. */
1178
+ private ownRouterOf;
1179
+ /** Records the app a pack's routes run under, for the keywords written on its construction. */
1180
+ recordApp(pack: string, call: Node, file: string): void;
1181
+ /**
1182
+ * Every call that registers something for a route on the way to it:
1183
+ * the app's construction, the mount calls and the routers they mount
1184
+ * onto, and the route's own router. A caller reads a keyword off each.
1185
+ */
1186
+ callsAbove(query: RouteWrapperQuery): Node[];
1067
1187
  /**
1068
1188
  * Where the decorator's object was built: the route's own file, the file
1069
1189
  * a project wrapper function is written in, or the file an imported
@@ -1309,7 +1429,7 @@ interface ExtractPythonOptions {
1309
1429
  onExtractionReport?: (report: ExtractionReport) => void;
1310
1430
  /** Called once with what the cache decided, for `suss extract --timing`. */
1311
1431
  onCacheDiagnostic?: (diagnostic: CacheDiagnostic) => void;
1312
- /** Absolute. `<projectRoot>/.suss/cache` by default; `null` turns it off. */
1432
+ /** Absolute. `<projectRoot>/.suss/cache` by default, or under `SUSS_CACHE_DIR`; `null` turns it off. */
1313
1433
  cacheDir?: string | null;
1314
1434
  /** The modules the project lists in `suss.json`, with absolute paths. */
1315
1435
  modules?: readonly DeclaredModule[];