@suss/adapter-python 0.3.2-0 → 0.4.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 +89 -0
- package/dist/index.cjs +16771 -191
- package/dist/index.d.cts +70 -13
- package/dist/index.d.ts +70 -13
- package/dist/index.js +16772 -190
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -101,6 +101,95 @@ A registration nobody wrote leaves the reading standing rather than abstaining.
|
|
|
101
101
|
|
|
102
102
|
A prefix written with a trailing slash leaves the composed path with two. Werkzeug answers such a rule at the merged path and redirects the written one, so `/api/v1//orders` is reached at `/api/v1/orders`; the pack says so, and the reader merges repeated slashes in every path it composes. Starlette does not do this, so FastAPI's pack says nothing and its paths stand as composed.
|
|
103
103
|
|
|
104
|
+
## What a body lowers to
|
|
105
|
+
|
|
106
|
+
A route's own returns are what make it produce more than one transition, so the
|
|
107
|
+
adapter lowers a function body into the statement form the shared path engine
|
|
108
|
+
in `@suss/extractor` walks. That engine is generic over the language's own
|
|
109
|
+
condition handle and never looks inside one, so the enumeration, the negation
|
|
110
|
+
of an earlier arm, and the budget are all shared with TypeScript.
|
|
111
|
+
|
|
112
|
+
| Python | Lowers to |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `if` / `elif` / `else` | one `if` per test, with the elif chain nested into the else arm the way Python reads it |
|
|
115
|
+
| `while`, `for` | `loop` |
|
|
116
|
+
| `try` / `except` / `finally` | `try`, with every except arm as the catch body |
|
|
117
|
+
| `match` / `case` | `switch`, one group per case, `case _` as the default group |
|
|
118
|
+
| `return`, `raise` | `exit`, which is what gives each return its own transition |
|
|
119
|
+
| `break`, `continue` | `exit`, which the engine uses for reachability rather than as an outcome |
|
|
120
|
+
| anything else | `opaque` |
|
|
121
|
+
|
|
122
|
+
A statement's exit kind comes from scanning its own subtree for a return or a
|
|
123
|
+
raise, stopping at a nested `def` or `lambda`, because those belong to the
|
|
124
|
+
function they declare. A raise anywhere beats a return anywhere.
|
|
125
|
+
|
|
126
|
+
So a handler written this way:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
def get(self, order_id) -> dict:
|
|
130
|
+
if not found:
|
|
131
|
+
return {"error": "nope"}, 404
|
|
132
|
+
return {"a": 1}, 200
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
comes out as two transitions, the 404 gated on the opaque condition
|
|
136
|
+
`not found` and the 200 gated on its negation, rather than as one claim with a
|
|
137
|
+
guessed status.
|
|
138
|
+
|
|
139
|
+
### Keying anything on a node
|
|
140
|
+
|
|
141
|
+
tree-sitter hands back a fresh wrapper object every time a child is read, so
|
|
142
|
+
two reads of one node are never `===` and a plain `Set` or `Map` keyed on a
|
|
143
|
+
node matches nothing. Use `NodeSet` and `NodeMap`, which key on the node id.
|
|
144
|
+
`npm run check:style` fails a build that keys either on a node.
|
|
145
|
+
|
|
146
|
+
## What a body does with the database
|
|
147
|
+
|
|
148
|
+
A pack says which query types its library defines, and a call chain matches
|
|
149
|
+
when the method behind it says it returns one:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
storage: [
|
|
153
|
+
{
|
|
154
|
+
module: "sqlalchemy.orm",
|
|
155
|
+
queryTypes: ["Query"],
|
|
156
|
+
writes: ["update", "delete", "add"],
|
|
157
|
+
storageSystem: "postgres",
|
|
158
|
+
},
|
|
159
|
+
]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Matching on the return rather than on the import is what reads a project's own
|
|
163
|
+
wrapper. A measured Flask service imports `sqlalchemy` in 50 files, and every
|
|
164
|
+
one of its 157 queries still goes through a base class the call sites never
|
|
165
|
+
import:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# in the project, not in the library
|
|
169
|
+
class Base:
|
|
170
|
+
@classmethod
|
|
171
|
+
def query(cls) -> Query: ...
|
|
172
|
+
|
|
173
|
+
class Orders(Base): ...
|
|
174
|
+
|
|
175
|
+
# in a handler, importing neither the base nor SQLAlchemy
|
|
176
|
+
found = Orders.query().filter_by(id=1).first()
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
A recognizer keyed on the import finds none of that. Following the call to
|
|
180
|
+
`Base.query` and reading what it says it returns finds all of it, and the
|
|
181
|
+
inheritance hop comes from `contains`, which reads `holdsProperty` and adds
|
|
182
|
+
what a base class declares.
|
|
183
|
+
|
|
184
|
+
A chain is one thing the code does, so the three calls above are one read. The
|
|
185
|
+
call that starts the chain is the one resolved, and the method the chain ends
|
|
186
|
+
with tells a read from a write. A method a base and a subclass both declare
|
|
187
|
+
gives two, and nothing is claimed.
|
|
188
|
+
|
|
189
|
+
`fields` and `selector` come back empty, so an effect says which model and
|
|
190
|
+
which operation and not which columns. Raw SQL handed to `execute` is not read
|
|
191
|
+
at all.
|
|
192
|
+
|
|
104
193
|
## Where a mount is written
|
|
105
194
|
|
|
106
195
|
Almost no service mounts anything at the top level of a module. It builds its routers or namespaces there, each with a literal prefix, and registers them inside the function that builds the app, often by looping over a list that another function put together:
|