@suss/adapter-python 0.11.0 → 0.13.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/README.md CHANGED
@@ -6,7 +6,7 @@ The Python language adapter for suss. It parses source with tree-sitter (WASM),
6
6
 
7
7
  `@suss/adapter-python` is the Python language adapter. It meets the Layer 1 contract in [`docs/internal/facts-and-rules.md`](../../../docs/internal/facts-and-rules.md): discover units, emit summaries in the shared IR, emit facts. It parses a file with `web-tree-sitter` and a vendored Python grammar (`grammar/tree-sitter-python.wasm`, so there is no native build step), builds module, class and function scopes over the tree (imports, assignments, `global`/`nonlocal`), works out which file in the repo an import points at, and finds routes on decorated functions and class methods whose decorator resolves to a module the pack was configured with. When a pack says the library mounts routers (FastAPI's `APIRouter` plus `include_router`), the adapter builds a route's path out of the two literal prefixes, one mount hop deep. When it cannot build the path, the route keeps its name, gets no path, and gets a gap explaining why. Each unit it finds becomes a `RawCodeStructure` object, which it hands to `assembleSummary` in `@suss/extractor`, the same assembly code the TypeScript adapter uses.
8
8
 
9
- v0 (this slice) does nothing with the path engine. A route either has no transitions at all, or one transition that describes the response the code declares (a FastAPI `response_model` / `status_code`, or a return annotation). It never breaks a route into branches. See [`docs/internal/proposals/language-adapters.md`](../../../docs/internal/proposals/language-adapters.md) for what a later slice adds.
9
+ v0 (this slice) does nothing with the path engine. A route either has no transitions at all, or one transition that describes the response the code declares (a FastAPI `response_model` / `status_code`, or a return annotation). It never breaks a route into branches. See [`design/proposals/language-adapters.md`](../../../design/proposals/language-adapters.md) for what a later slice adds.
10
10
 
11
11
  ## Where it fits in suss
12
12
 
@@ -81,6 +81,20 @@ FastAPI has no column here: its app has no prefix of its own, and the pack decla
81
81
  | `""`, `None`, `False`, `0` | the blueprint's prefix + the rest | unstated |
82
82
  | a name or a call | whatever it evaluates to | unreadable |
83
83
 
84
+ ### A call that spreads a dictionary
85
+
86
+ `Api(**build_authorizations())` and `include_router(router, **options)`
87
+ spread a value the call does not write out. The reader skips the spread
88
+ and reads the keywords that are written, so the routes underneath keep
89
+ their paths.
90
+
91
+ That leaves one thing it cannot see. If the dictionary sets the prefix
92
+ keyword itself, the composed path is wrong, and nothing in the output
93
+ says so. Reading the prefix as unknown instead would be correct and would
94
+ cost every route under every call that spreads a config dictionary, which
95
+ is most of them. Propagating the uncertainty rather than choosing between
96
+ the two is the work that would settle this.
97
+
84
98
  **At the registration** (`app.register_blueprint(bp, ...)`), which the reader consults only to abstain:
85
99
 
86
100
  | Written | flask-restx serves | The reader says |
package/dist/index.cjs CHANGED
@@ -622,6 +622,9 @@ function readCallArguments(argumentList) {
622
622
  }
623
623
  continue;
624
624
  }
625
+ if (child.type === "dictionary_splat" || child.type === "list_splat") {
626
+ continue;
627
+ }
625
628
  args.push(readArg(child));
626
629
  }
627
630
  return { args, keywordArgs };
@@ -19248,7 +19251,8 @@ function recordMountStatement(stmt, position, scan) {
19248
19251
  scan.composition,
19249
19252
  position.site
19250
19253
  );
19251
- for (const target of mountedConstructions(args[0], position, scan)) {
19254
+ const mounted = args[0] ?? (scan.composition.routerKeyword === void 0 ? void 0 : keywordArgs[scan.composition.routerKeyword]);
19255
+ for (const target of mountedConstructions(mounted, position, scan)) {
19252
19256
  recordMount(scan.index, target, state);
19253
19257
  }
19254
19258
  }
package/dist/index.d.cts CHANGED
@@ -304,6 +304,12 @@ interface RouterComposition {
304
304
  routerConstructorName: string;
305
305
  /** Method that mounts a router onto the app, FastAPI's `include_router`. */
306
306
  includeMethodName: string;
307
+ /**
308
+ * What the mount method calls its router parameter, FastAPI's `router`.
309
+ * A call that passes the router by keyword rather than by position is
310
+ * read through this. Unset reads the first argument only.
311
+ */
312
+ routerKeyword?: string;
307
313
  /** One keyword serves the constructor and the mount alike. A library that spells them apart needs two fields here. */
308
314
  prefixKeyword: string;
309
315
  /** Default "prefixes". */
package/dist/index.d.ts CHANGED
@@ -304,6 +304,12 @@ interface RouterComposition {
304
304
  routerConstructorName: string;
305
305
  /** Method that mounts a router onto the app, FastAPI's `include_router`. */
306
306
  includeMethodName: string;
307
+ /**
308
+ * What the mount method calls its router parameter, FastAPI's `router`.
309
+ * A call that passes the router by keyword rather than by position is
310
+ * read through this. Unset reads the first argument only.
311
+ */
312
+ routerKeyword?: string;
307
313
  /** One keyword serves the constructor and the mount alike. A library that spells them apart needs two fields here. */
308
314
  prefixKeyword: string;
309
315
  /** Default "prefixes". */
package/dist/index.js CHANGED
@@ -568,6 +568,9 @@ function readCallArguments(argumentList) {
568
568
  }
569
569
  continue;
570
570
  }
571
+ if (child.type === "dictionary_splat" || child.type === "list_splat") {
572
+ continue;
573
+ }
571
574
  args.push(readArg(child));
572
575
  }
573
576
  return { args, keywordArgs };
@@ -19204,7 +19207,8 @@ function recordMountStatement(stmt, position, scan) {
19204
19207
  scan.composition,
19205
19208
  position.site
19206
19209
  );
19207
- for (const target of mountedConstructions(args[0], position, scan)) {
19210
+ const mounted = args[0] ?? (scan.composition.routerKeyword === void 0 ? void 0 : keywordArgs[scan.composition.routerKeyword]);
19211
+ for (const target of mountedConstructions(mounted, position, scan)) {
19208
19212
  recordMount(scan.index, target, state);
19209
19213
  }
19210
19214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-python",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "Python language adapter for suss — parses source with tree-sitter (WASM), resolves names with a lexical binder, and emits raw extractions for the suss pipeline.",
6
6
  "keywords": [
@@ -47,10 +47,10 @@
47
47
  "typecheck": "tsc --noEmit"
48
48
  },
49
49
  "dependencies": {
50
- "@suss/behavioral-ir": "0.11.0",
51
- "@suss/datalog": "0.11.0",
52
- "@suss/extractor": "0.11.0",
53
- "@suss/sql": "0.11.0",
50
+ "@suss/behavioral-ir": "0.13.0",
51
+ "@suss/datalog": "0.13.0",
52
+ "@suss/extractor": "0.13.0",
53
+ "@suss/sql": "0.13.0",
54
54
  "web-tree-sitter": "^0.26.11"
55
55
  },
56
56
  "devDependencies": {