@suss/adapter-python 0.21.1 → 0.22.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 +71 -0
- package/dist/index.cjs +2183 -689
- package/dist/index.d.cts +148 -3
- package/dist/index.d.ts +148 -3
- package/dist/index.js +1831 -309
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -298,6 +298,77 @@ Both of those qualifiers matter. Only counting loops keeps the ordinary case wor
|
|
|
298
298
|
|
|
299
299
|
A module-level mount is never dropped this way. It runs whichever factory the app calls. flask-restx trims at the constructor and not at the mount, because only the constructor's path goes through the property that strips it. The pack declares trimming per library, and the mount side does not need it, since a mount that states a prefix on that library abstains anyway.
|
|
300
300
|
|
|
301
|
+
## What a file reads from the environment
|
|
302
|
+
|
|
303
|
+
`os.environ` is part of the language's standard library, so the adapter recognizes reads of it itself, without a pack. Each read becomes the same `config-read` interaction the TypeScript adapter emits for `process.env.X`, on the binding `runtime-config`, spelled `os.environ["X"]` whichever way the source wrote it. The runtime-config checker pairs those against what a template declares for the function the file runs in.
|
|
304
|
+
|
|
305
|
+
| Python | Recognized as | Defaulted |
|
|
306
|
+
| --- | --- | --- |
|
|
307
|
+
| `os.environ["X"]` | a read of `X` | no |
|
|
308
|
+
| `os.environ.get("X")` | a read of `X` | no |
|
|
309
|
+
| `os.environ.get("X", "d")`, `os.environ.get("X", default="d")` | a read of `X` | yes |
|
|
310
|
+
| `os.getenv("X")`, `os.getenv("X", "d")` | a read of `X` | as above |
|
|
311
|
+
| any of these followed by `or` (`os.environ.get("X") or "d"`) | a read of `X` | yes |
|
|
312
|
+
| `import os as _os`, `from os import environ, getenv` | the same reads, through the alias | |
|
|
313
|
+
| `os.environ[name]`, `os.environ.get(f"{prefix}_X")` | nothing: the name is not a literal | |
|
|
314
|
+
| `os.environ["X"] = "1"`, `del os.environ["X"]`, `"X" in os.environ` | nothing: a write or a membership test | |
|
|
315
|
+
| `os.environ.get("X") or os.environ.get("Y")` | `X` defaulted, `Y` not, since `Y` is the chain's last resort | |
|
|
316
|
+
|
|
317
|
+
A read inside a route body goes on that route's summary. A read at module level, or in a class body, runs when the module is imported, so it goes on a `module-init` summary named after the file, one per file that has such a read. A read inside a function a route reaches through its calls goes on that function's own summary (see below). A read inside a function nothing discovered and nothing reaches is reported nowhere, because nothing says when that function runs.
|
|
318
|
+
|
|
319
|
+
## What a route reaches
|
|
320
|
+
|
|
321
|
+
A route's body calls project functions, and those call others. Each function a route reaches this way gets a summary of its own, of kind `library`, bound as `function-call` with `transport: "in-process"` and `recognition: "reachable"`, with the calls, environment reads and database work its own body does. Each invocation effect on a route or a reached function says which summary the call lands on, in `summary`, so a reader answering "what does this route reach" follows `summary` from one unit to the next and never has to match a name. This is the same walk the TypeScript adapter runs, and it produces the same shape.
|
|
322
|
+
|
|
323
|
+
The walk starts at every discovered route, and adds a `calls` fact for each call in a body it could follow, until the set stops growing. A function two routes both reach gets one summary. A call the walk could not follow is recorded once per callee on the summary of the body it is in, as an `unfollowedCall` gap saying why, unless the reason is one nothing could have done better with (a call to a parameter that some caller passes a function by name into, or a call into a package that is not in the run).
|
|
324
|
+
|
|
325
|
+
A callee is followed only through a binding that says where it came from. The binder's scopes settle a name written in the file; the import resolver settles a name brought in from another file in the run.
|
|
326
|
+
|
|
327
|
+
| Written as | Followed to |
|
|
328
|
+
| --- | --- |
|
|
329
|
+
| `helper()` with `def helper` in the file | that definition |
|
|
330
|
+
| `helper()` with `from pkg.mod import helper` | `helper` in `pkg/mod.py` |
|
|
331
|
+
| `mod.helper()` with `import pkg.mod` or `from pkg import mod` | `helper` in `pkg/mod.py`, whether or not `pkg` has an `__init__.py` |
|
|
332
|
+
| `run = helper` then `run()`, written straight in a body | `helper`, one alias hop |
|
|
333
|
+
| `Handlers.on_get()` with `on_get = helper` in the class body | `helper` |
|
|
334
|
+
| `self.method()` or `cls.method()` inside a method | the method on the enclosing class |
|
|
335
|
+
| `Service().run()`, or `svc = Service()` then `svc.run()` | `run` on `Service` |
|
|
336
|
+
| `Service()` | `Service.__init__` |
|
|
337
|
+
| `helper()` with `from pkg.mod import *` and one module defining it | that definition |
|
|
338
|
+
| a call written as an argument, or as the receiver of a chain | followed like any other, in the order it finishes |
|
|
339
|
+
| a call inside a lambda | the enclosing function's call |
|
|
340
|
+
| `inner()` with `def inner` nested in the body | `inner`, which gets a summary of its own with the calls its body makes |
|
|
341
|
+
| `register(build_index)`, where `register(handler)` calls `handler()` | `build_index`, followed from wherever a caller in the run named it, through the parameter `register`'s own body calls |
|
|
342
|
+
|
|
343
|
+
A function passed by name into a call is followed one hop further than the call itself. `register(build_index)` records where `build_index` is declared, keyed to its position among `register`'s arguments; `register`'s own body calling `handler()` records which of its parameters that call goes through. A reader asking what calls `build_index` finds `register`, and what `register` reaches includes `build_index`, even though the join crosses two separate calls. Only a bare name counts, so a lambda written inline and a variable bound to a function are both out of scope, the same as a callee written that way.
|
|
344
|
+
|
|
345
|
+
Where it stops, and what the gap says:
|
|
346
|
+
|
|
347
|
+
| Written as | Reason |
|
|
348
|
+
| --- | --- |
|
|
349
|
+
| a call on a parameter, `callback()`, that some caller in the run passes a function by name into | followed through the join above (no gap) |
|
|
350
|
+
| a call on a parameter, `callback()`, that no caller in the run passes a function by name into | the caller supplies it, and nothing named what it passed |
|
|
351
|
+
| a name a `for`, `with`, `except ... as`, walrus, or lambda parameter rebinds | the value could not be settled |
|
|
352
|
+
| a name assigned inside an `if`, `try`, or `with` block, or by unpacking | the value could not be settled |
|
|
353
|
+
| `from a import *` and `from b import *` when both define the name | more than one possible source |
|
|
354
|
+
| an import that resolves under two roots | more than one possible source |
|
|
355
|
+
| a call into a package whose source is not in the run | outside the run (no gap) |
|
|
356
|
+
| a value's attribute, `order.save()`, or a call on a call's result | no declaration to follow (no gap) |
|
|
357
|
+
|
|
358
|
+
One callee spelling that lands on two definitions in one body, `load()` under a class body that imports its own `load` say, is placed on neither.
|
|
359
|
+
|
|
360
|
+
Not followed yet: a method inherited from a base class, a callable stored in a dict or a list, a decorator's own body, a call written in a parameter default such as `Depends(get_db)`, and an attribute set on `self` in `__init__` and called elsewhere.
|
|
361
|
+
|
|
362
|
+
A summary's `identity.id` is the file relative to the project root plus the export path (`app/store.py::read_orders`, `app/models.py::Orders.total`), and `summary` on an effect is that id. Two summaries that would share an id are told apart by their boundary, then by their line.
|
|
363
|
+
|
|
364
|
+
## What a file imports from the project
|
|
365
|
+
|
|
366
|
+
Every summary has `metadata.moduleImports`, the project files its own file's imports resolved to, relative to the workspace root and sorted. A file whose imports all resolve outside the project gets an empty list rather than no field, so a Lambda handler that imports only the standard library still tells the checker that its closure is the handler module alone. A checker rebuilds the import graph from that field to work out which modules a template's handler entry loads; the entry `app.handler` under `CodeUri: src/` matches `src/app.py`, and a dotted module such as `shop.app.handler` matches `src/shop/app.py`.
|
|
367
|
+
|
|
368
|
+
## Extraction cache
|
|
369
|
+
|
|
370
|
+
This adapter uses the extraction cache shared across every language adapter, from `@suss/extractor`. It keeps the cache beside the project root, at `.suss/cache/`, and supplies the walked `.py` file list as the key's file set; it has no config file of its own to guard the entry the way the TypeScript adapter's tsconfig does. Reuse today is whole-run only: any file changing re-extracts the whole project instead of that one file. See the extractor package's README for the design.
|
|
371
|
+
|
|
301
372
|
## Grammar asset
|
|
302
373
|
|
|
303
374
|
`grammar/tree-sitter-python.wasm` is a checked-in binary asset, not a build output. See [`grammar/README.md`](./grammar/README.md) for its provenance and how to bump it.
|