@suss/adapter-python 0.25.0 → 0.27.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
@@ -25,167 +25,43 @@ declare function preloadPythonGrammar(): Promise<void>;
25
25
  declare function parsePythonSync(source: string): Tree;
26
26
 
27
27
  /**
28
- * The lexical binder. It builds module, class, and function scopes over one
29
- * file's parse tree and records how each name in each scope came to be bound,
30
- * whether that was an import, an assignment, a def, a parameter, or a `global`
31
- * or `nonlocal` redirect to another scope.
28
+ * Maps a dotted import onto the file in the repo it refers to.
32
29
  *
33
- * It never has to be complete, because "I could not resolve this name" is a
34
- * legal answer everywhere a name is read. What it has to avoid is being wrong.
35
- * So it only binds names written directly in a body's own statement list. A
36
- * definition or import nested inside an `if`, `try`, or `with` block is not
37
- * found, and a read of it comes back unresolved rather than as a guess.
38
- */
39
-
40
- type ScopeKind = "module" | "class" | "function";
41
- interface Scope {
42
- kind: ScopeKind;
43
- /** The module node, or the class_definition / function_definition this scope belongs to. */
44
- node: Node;
45
- parent: Scope | null;
46
- bindings: Map<string, Binding>;
47
- }
48
- /**
49
- * `relativeLevel` is 0 for an absolute import and the dot count for a
50
- * relative one. `module` is the dotted path after the dots, empty for
51
- * a bare `from . import c`.
30
+ * The search walks a small set of configured roots. Finding out what `sys.path`
31
+ * actually is would mean running Python, so when more than one root offers a
32
+ * candidate, the resolver abstains rather than picking one.
52
33
  */
53
- type Binding = {
54
- kind: "import";
55
- module: string;
56
- relativeLevel: number;
57
- localName: string;
58
- } | {
59
- kind: "importFrom";
34
+ interface RelativeModuleSpec {
35
+ /** The dotted path after the leading dots. It is empty for a bare `from . import x`. */
60
36
  module: string;
37
+ /** Dot count: 0 means absolute, 1 is `.`, 2 is `..`, and so on. */
61
38
  relativeLevel: number;
62
- importedName: string;
63
- } | {
64
- kind: "classDef";
65
- node: Node;
66
- } | {
67
- kind: "functionDef";
68
- node: Node;
69
- } | {
70
- kind: "parameter";
71
- }
72
- /** The right-hand side, when there is one, so we can trace a decorator's base object one hop back to whatever constructed it. */
73
- | {
74
- kind: "assignment";
75
- value: Node | null;
76
- }
77
- /** `global x` inside a function: reads of `x` in this scope resolve in the module scope instead. */
78
- | {
79
- kind: "global";
80
- }
81
- /** `nonlocal x` inside a function: reads of `x` resolve in the nearest enclosing function scope. */
82
- | {
83
- kind: "nonlocal";
84
- };
85
- interface ModuleBinding {
86
- moduleScope: Scope;
87
- /** The scope a class_definition, a function_definition, or the module node opens, keyed by that node's id. */
88
- scopeFor: Map<number, Scope>;
89
- /** The modules a `from X import *` pulls in. We cannot list what a wildcard brings in, so nothing expands them here. */
90
- openImports: string[];
91
- }
92
- declare function bindModule(root: Node): ModuleBinding;
93
- /**
94
- * Python does not put a class body's namespace on the lookup chain of a function
95
- * nested inside it, so a class scope's own bindings only count when the search
96
- * starts there. A `global` or `nonlocal` marker sends the lookup to the scope it
97
- * points at.
98
- */
99
- declare function resolveName(scope: Scope, name: string): Binding | null;
100
-
101
- /**
102
- * Turns a Python annotation into an IR type shape.
103
- *
104
- * Nothing here infers anything. A value nobody annotated has no shape at all,
105
- * and an annotation this module does not recognize comes back as a `ref` by
106
- * name, which says only what the source called it.
107
- */
108
-
109
- /**
110
- * `definitions` stores each converted class shape once, however many annotations
111
- * mention it. `scopeFor` is here so a name written inside a referenced class's
112
- * body resolves too, not only one written at the annotation's use site.
113
- */
114
- interface AnnotationContext {
115
- scopeFor: Map<number, Scope>;
116
- definitions: Map<string, TypeShape | null>;
117
- }
118
- /** `scope` is where the annotation is written, which is how we tell a project-local class from an external name. */
119
- declare function annotationToShape(typeNode: Node, scope: Scope, ctx: AnnotationContext): TypeShape;
120
- /** Exported so a decorator keyword that gives a class name reads the same way as that name written in annotation position. */
121
- declare function shapeFromName(name: string, scope: Scope, ctx: AnnotationContext): TypeShape;
122
-
123
- interface Range {
124
- start: number;
125
- end: number;
126
39
  }
127
-
128
- /** An argument as written, plus its node so a reader can evaluate what it comes down to. */
129
- type DecoratorArg = DecoratorArgShape & {
130
- readonly node: Node;
131
- };
132
- type DecoratorArgShape = {
133
- kind: "string";
134
- value: string;
135
- } | {
136
- kind: "number";
137
- value: number;
138
- } | {
139
- kind: "boolean";
140
- value: boolean;
141
- } | {
142
- kind: "none";
143
- }
144
- /** A bare name. The caller resolves it, using the scope it already has. */
145
- | {
146
- kind: "identifier";
147
- name: string;
148
- }
149
- /** One dotted hop, `routers.orders`. The caller decides what the object is. */
150
- | {
151
- kind: "attribute";
152
- objectName: string;
153
- attributeName: string;
154
- } | {
155
- kind: "list";
156
- items: DecoratorArg[];
40
+ type ModuleResolution = {
41
+ status: "resolved";
42
+ file: string;
157
43
  } | {
158
- kind: "other";
159
- };
160
- interface DecoratorClassification {
161
- /** The name as its source module exports it, not the local alias or attribute path it was written under. */
162
- importedName: string | null;
163
- /** The dotted module the name was imported from. It is null whenever `importedName` is null. */
164
- module: string | null;
165
- /** The local variable an attribute decorator hangs on, `app` in `@app.get(...)`. */
166
- objectName: string | null;
167
- relativeLevel: number;
168
- args: DecoratorArg[];
169
- keywordArgs: Record<string, DecoratorArg>;
170
- /** Where the decorator is written. Anything we read out of its arguments uses this as its provenance. */
171
- range: Range;
172
- /**
173
- * The module the decorator's object lives in, when the decorator was read
174
- * through a project wrapper written in another file. The router prefix is
175
- * resolved there, since that is where the namespace is constructed.
176
- */
177
- objectModule?: ModuleBinding;
44
+ status: "unresolved";
178
45
  /**
179
- * The call the rules say built the object this decorator hangs on, when
180
- * the lexical scope could not say. A router prefix is looked up by this
181
- * call rather than by a variable name, because there may not be one.
46
+ * "external": no configured root has a file for it. "ambiguous": more
47
+ * than one does. "outsideRoots": a relative import had enough dots to walk
48
+ * up past every configured root.
182
49
  */
183
- subjectConstruction?: {
184
- key: string;
185
- constructorName: string;
186
- };
50
+ reason: "external" | "ambiguous" | "outsideRoots";
51
+ };
52
+ interface ModuleResolverOptions {
53
+ /** The directories an absolute dotted import is resolved against. We cannot see `sys.path` order, since that only exists at runtime. */
54
+ roots: string[];
187
55
  }
188
- declare function classifyDecorator(decoratorNode: Node, scope: Scope): DecoratorClassification;
56
+ declare function resolveAbsoluteModule(dotted: string, options: ModuleResolverOptions): ModuleResolution;
57
+ /**
58
+ * The search starts in the importing file's own directory, because Python
59
+ * resolves a relative import against `__package__` and not against `sys.path`.
60
+ * `options.roots` only limits how far the dots can walk up, so a deeply relative
61
+ * import cannot land in an unrelated checkout.
62
+ */
63
+ declare function resolveRelativeModule(importingFile: string, spec: RelativeModuleSpec, options: ModuleResolverOptions): ModuleResolution;
64
+ declare function resolveModule(importingFile: string, spec: RelativeModuleSpec, options: ModuleResolverOptions): ModuleResolution;
189
65
 
190
66
  /**
191
67
  * The Python adapter's own pattern-pack contract.
@@ -312,7 +188,7 @@ interface PyClientCall {
312
188
  };
313
189
  /** Constructors whose instances take the same calls, `Session`. */
314
190
  receiverConstructors?: string[];
315
- /** What the response object gives a caller, so a guard on it reads as a guard on a status. */
191
+ /** What the response object gives a caller, so a guard on one of its members counts as a guard on a status. */
316
192
  response?: PyClientResponse;
317
193
  }
318
194
  /**
@@ -355,7 +231,7 @@ interface RouteConventions {
355
231
  defaultStatusCode?: number;
356
232
  /**
357
233
  * Set it only when the library reads a status out of the tuple a handler
358
- * returns, which is what Flask does with `return body, 201`. Without it
234
+ * returns, as Flask does with `return body, 201`. Without it
359
235
  * the library default applies whatever the body returns, and a route
360
236
  * that sets its own status would be reported at the default.
361
237
  */
@@ -375,7 +251,7 @@ interface RouteConventions {
375
251
  * own status; a return of anything else keeps the declared one.
376
252
  */
377
253
  responseConstructors?: PyStatusCall[];
378
- /** Unset means the library has no router mounting, and a route's decorator path stands as written. */
254
+ /** Unset means the library has no router mounting, and a route's decorator path is used as written. */
379
255
  routerComposition?: RouterComposition;
380
256
  /** The ways the library runs a project's own function around a route. The README lists what each one covers. */
381
257
  wrappers?: PyWrapperForm[];
@@ -431,7 +307,7 @@ interface PyDecoratedWrapperForm {
431
307
  continuationParam?: number;
432
308
  /**
433
309
  * Set when a value returned ends the request and only a bare return
434
- * hands on, which is what Flask does with `before_request`.
310
+ * hands on. Flask's `before_request` works this way.
435
311
  */
436
312
  returnedValueResponds?: boolean;
437
313
  /** The position the library hands the raised exception at. Set on an error handler, which runs only when the handler raised. */
@@ -576,57 +452,78 @@ type NoValuePrefix = "unstated" | "unreadable";
576
452
  type PathRepeatedSlashes = "kept" | "merged";
577
453
 
578
454
  /**
579
- * What discovery uses for one file. `factsPath` is the path the facts were
580
- * keyed under, which is the absolute one, while a summary shows the short one.
455
+ * The lexical binder. It builds module, class, and function scopes over one
456
+ * file's parse tree and records how each name in each scope came to be bound,
457
+ * whether that was an import, an assignment, a def, a parameter, or a `global`
458
+ * or `nonlocal` redirect to another scope.
459
+ *
460
+ * It never has to be complete, because "I could not resolve this name" is a
461
+ * legal answer everywhere a name is read. What it has to avoid is being wrong.
462
+ * So it only binds names written directly in a body's own statement list. A
463
+ * definition or import nested inside an `if`, `try`, or `with` block is not
464
+ * found, and a read of it comes back unresolved rather than as a guess.
581
465
  */
582
- interface StorageLookup {
583
- readonly facts: Database;
584
- readonly factsPath: string;
585
- readonly patterns: readonly StoragePattern[];
586
- readonly definitionAt: (key: string) => Node | undefined;
587
- readonly couldMatch: ReadonlySet<string>;
588
- /** What a pack says about statements a project writes as SQL itself. */
589
- readonly rawSql?: readonly RawSqlPattern[];
590
- }
591
466
 
467
+ type ScopeKind = "module" | "class" | "function";
468
+ interface Scope {
469
+ kind: ScopeKind;
470
+ /** The module node, or the class_definition / function_definition this scope belongs to. */
471
+ node: Node;
472
+ parent: Scope | null;
473
+ bindings: Map<string, Binding>;
474
+ }
592
475
  /**
593
- * Maps a dotted import onto the file in the repo it refers to.
594
- *
595
- * The search walks a small set of configured roots. Finding out what `sys.path`
596
- * actually is would mean running Python, so when more than one root offers a
597
- * candidate, the resolver abstains rather than picking one.
476
+ * `relativeLevel` is 0 for an absolute import and the dot count for a
477
+ * relative one. `module` is the dotted path after the dots, empty for
478
+ * a bare `from . import c`.
598
479
  */
599
- interface RelativeModuleSpec {
600
- /** The dotted path after the leading dots. It is empty for a bare `from . import x`. */
480
+ type Binding = {
481
+ kind: "import";
601
482
  module: string;
602
- /** Dot count: 0 means absolute, 1 is `.`, 2 is `..`, and so on. */
603
483
  relativeLevel: number;
604
- }
605
- type ModuleResolution = {
606
- status: "resolved";
607
- file: string;
484
+ localName: string;
608
485
  } | {
609
- status: "unresolved";
610
- /**
611
- * "external": no configured root has a file for it. "ambiguous": more
612
- * than one does. "outsideRoots": a relative import had enough dots to walk
613
- * up past every configured root.
614
- */
615
- reason: "external" | "ambiguous" | "outsideRoots";
486
+ kind: "importFrom";
487
+ module: string;
488
+ relativeLevel: number;
489
+ importedName: string;
490
+ } | {
491
+ kind: "classDef";
492
+ node: Node;
493
+ } | {
494
+ kind: "functionDef";
495
+ node: Node;
496
+ } | {
497
+ kind: "parameter";
498
+ }
499
+ /** The right-hand side, when there is one, so we can trace a decorator's base object one hop back to whatever constructed it. */
500
+ | {
501
+ kind: "assignment";
502
+ value: Node | null;
503
+ }
504
+ /** `global x` inside a function: reads of `x` in this scope resolve in the module scope instead. */
505
+ | {
506
+ kind: "global";
507
+ }
508
+ /** `nonlocal x` inside a function: reads of `x` resolve in the nearest enclosing function scope. */
509
+ | {
510
+ kind: "nonlocal";
616
511
  };
617
- interface ModuleResolverOptions {
618
- /** The directories an absolute dotted import is resolved against. We cannot see `sys.path` order, since that only exists at runtime. */
619
- roots: string[];
512
+ interface ModuleBinding {
513
+ moduleScope: Scope;
514
+ /** The scope a class_definition, a function_definition, or the module node opens, keyed by that node's id. */
515
+ scopeFor: Map<number, Scope>;
516
+ /** The modules a `from X import *` pulls in. We cannot list what a wildcard brings in, so nothing expands them here. */
517
+ openImports: string[];
620
518
  }
621
- declare function resolveAbsoluteModule(dotted: string, options: ModuleResolverOptions): ModuleResolution;
519
+ declare function bindModule(root: Node): ModuleBinding;
622
520
  /**
623
- * The search starts in the importing file's own directory, because Python
624
- * resolves a relative import against `__package__` and not against `sys.path`.
625
- * `options.roots` only limits how far the dots can walk up, so a deeply relative
626
- * import cannot land in an unrelated checkout.
521
+ * Python does not put a class body's namespace on the lookup chain of a function
522
+ * nested inside it, so a class scope's own bindings only count when the search
523
+ * starts there. A `global` or `nonlocal` marker sends the lookup to the scope it
524
+ * points at.
627
525
  */
628
- declare function resolveRelativeModule(importingFile: string, spec: RelativeModuleSpec, options: ModuleResolverOptions): ModuleResolution;
629
- declare function resolveModule(importingFile: string, spec: RelativeModuleSpec, options: ModuleResolverOptions): ModuleResolution;
526
+ declare function resolveName(scope: Scope, name: string): Binding | null;
630
527
 
631
528
  /** One file, already parsed and bound. `buildRouterIndex` takes a project as a list of these. */
632
529
  interface BoundPythonFile {
@@ -688,6 +585,137 @@ interface RouterIndexOptions extends ModuleResolverOptions {
688
585
  }
689
586
  declare function buildRouterIndex(files: BoundPythonFile[], packs: PythonPack[], resolverOptions: RouterIndexOptions): RouterIndex;
690
587
 
588
+ /**
589
+ * Where an imported name is defined, when the project defines it.
590
+ *
591
+ * The binder resolves a name inside one file only, so `item_in: ItemCreate`
592
+ * with `ItemCreate` imported from a models module comes back as an import
593
+ * binding and nothing more. This asks the resolution rules where the name
594
+ * came from, through any re-exports on the way, and returns the class or
595
+ * the assigned value the defining file binds it to, along with that file's
596
+ * scopes so the definition can be read in the scope it is written in.
597
+ */
598
+
599
+ interface ImportedDefinition {
600
+ /** The class_definition, or the value an assignment gave the name. */
601
+ node: Node;
602
+ /** The module scope of the file the definition is written in. */
603
+ moduleScope: Scope;
604
+ /** The scopes of that file, keyed by the node that opens each one. */
605
+ scopeFor: Map<number, Scope>;
606
+ }
607
+ /** `scope` is where the name is read, which is what keys the import in the facts. */
608
+ type ImportedDefinitionLookup = (scope: Scope, name: string) => ImportedDefinition | null;
609
+
610
+ /**
611
+ * Turns a Python annotation into an IR type shape.
612
+ *
613
+ * Nothing here infers anything. A value nobody annotated has no shape at all,
614
+ * and an annotation this module does not recognize comes back as a `ref` by
615
+ * name, which says only what the source called it.
616
+ */
617
+
618
+ /**
619
+ * `definitions` stores each converted class shape once, however many annotations
620
+ * mention it. `scopeMaps` is here so a name written inside a referenced class's
621
+ * body resolves too, not only one written at the annotation's use site; a class
622
+ * read from another file brings that file's scopes along. `importedDefinition`
623
+ * is absent when a caller reads one file on its own, and an imported name is
624
+ * then a ref by name and nothing more.
625
+ */
626
+ interface AnnotationContext {
627
+ scopeMaps: Map<number, Scope>[];
628
+ definitions: Map<string, TypeShape | null>;
629
+ importedDefinition: ImportedDefinitionLookup | null;
630
+ /** The alias values being expanded, so `A = B` and `B = A` end rather than recurse. */
631
+ expanding: Set<number>;
632
+ }
633
+ /** `scope` is where the annotation is written, which is how we tell a project-local class from an external name. */
634
+ declare function annotationToShape(typeNode: Node, scope: Scope, ctx: AnnotationContext): TypeShape;
635
+ /** Exported so a decorator keyword that gives a class name reads the same way as that name written in annotation position. */
636
+ declare function shapeFromName(name: string, scope: Scope, ctx: AnnotationContext): TypeShape;
637
+
638
+ interface Range {
639
+ start: number;
640
+ end: number;
641
+ }
642
+
643
+ /** An argument as written, plus its node so a reader can evaluate what it comes down to. */
644
+ type DecoratorArg = DecoratorArgShape & {
645
+ readonly node: Node;
646
+ };
647
+ type DecoratorArgShape = {
648
+ kind: "string";
649
+ value: string;
650
+ } | {
651
+ kind: "number";
652
+ value: number;
653
+ } | {
654
+ kind: "boolean";
655
+ value: boolean;
656
+ } | {
657
+ kind: "none";
658
+ }
659
+ /** A bare name. The caller resolves it, using the scope it already has. */
660
+ | {
661
+ kind: "identifier";
662
+ name: string;
663
+ }
664
+ /** One dotted hop, `routers.orders`. The caller decides what the object is. */
665
+ | {
666
+ kind: "attribute";
667
+ objectName: string;
668
+ attributeName: string;
669
+ } | {
670
+ kind: "list";
671
+ items: DecoratorArg[];
672
+ } | {
673
+ kind: "other";
674
+ };
675
+ interface DecoratorClassification {
676
+ /** The name as its source module exports it, not the local alias or attribute path it was written under. */
677
+ importedName: string | null;
678
+ /** The dotted module the name was imported from. It is null whenever `importedName` is null. */
679
+ module: string | null;
680
+ /** The local variable an attribute decorator hangs on, `app` in `@app.get(...)`. */
681
+ objectName: string | null;
682
+ relativeLevel: number;
683
+ args: DecoratorArg[];
684
+ keywordArgs: Record<string, DecoratorArg>;
685
+ /** Where the decorator is written. Anything we read out of its arguments uses this as its provenance. */
686
+ range: Range;
687
+ /**
688
+ * The module the decorator's object lives in, when the decorator was read
689
+ * through a project wrapper written in another file. The router prefix is
690
+ * resolved there, since that is where the namespace is constructed.
691
+ */
692
+ objectModule?: ModuleBinding;
693
+ /**
694
+ * The call the rules say built the object this decorator hangs on, when
695
+ * the lexical scope could not say. A router prefix is looked up by this
696
+ * call rather than by a variable name, because there may not be one.
697
+ */
698
+ subjectConstruction?: {
699
+ key: string;
700
+ constructorName: string;
701
+ };
702
+ }
703
+ declare function classifyDecorator(decoratorNode: Node, scope: Scope): DecoratorClassification;
704
+
705
+ /**
706
+ * What discovery uses for one file. `factsPath` is the path the facts were
707
+ * keyed under, which is the absolute one, while a summary shows the short one.
708
+ */
709
+ interface StorageLookup {
710
+ readonly facts: Database;
711
+ readonly factsPath: string;
712
+ readonly patterns: readonly StoragePattern[];
713
+ readonly definitionAt: (key: string) => Node | undefined;
714
+ readonly couldMatch: ReadonlySet<string>;
715
+ /** What a pack says about statements a project writes as SQL itself. */
716
+ readonly rawSql?: readonly RawSqlPattern[];
717
+ }
718
+
691
719
  /**
692
720
  * What a call's callee is, read through the binder and the import
693
721
  * facts: a function in this run the walk can step into, or a reason it
@@ -810,6 +838,8 @@ interface DiscoveryOptions {
810
838
  facts?: Database | undefined;
811
839
  /** 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
840
  wrappers?: PythonWrapperIndex | undefined;
841
+ /** Where a name another file defines is written, so an imported model or alias reads as the class or value behind it. Absent when a caller reads one file on its own. */
842
+ importedDefinition?: ImportedDefinitionLookup | undefined;
813
843
  }
814
844
  declare function discoverUnits(root: Node, module: ModuleBinding, options: DiscoveryOptions): RawCodeStructure[];
815
845