@suss/adapter-python 0.22.1 → 0.22.3

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
@@ -66,7 +66,8 @@ Every cell describes what the library itself does. We read that off the library'
66
66
  | `"/"` | the route path, no prefix | the app does not start | stated, and trimming leaves nothing |
67
67
  | `""` | `/` + the namespace's name | the route path, no prefix | unstated where the pack says a no-value prefix is unstated, otherwise stated |
68
68
  | `None`, `False`, `0` | `/` + the namespace's name | the app does not start | unstated where the pack says so, otherwise unreadable |
69
- | a name or a call | whatever it evaluates to | whatever it evaluates to | unreadable |
69
+ | a name, a call, or an f-string the evaluator settles | whatever it evaluates to | whatever it evaluates to | stated, with the string it settles on |
70
+ | anything the evaluator cannot settle | whatever it evaluates to | whatever it evaluates to | unreadable |
70
71
 
71
72
  **At the mount** (`add_namespace(ns, path=...)`, `include_router(router, prefix=...)`):
72
73
 
@@ -77,7 +78,8 @@ Every cell describes what the library itself does. We read that off the library'
77
78
  | `"/api/"` | `/api/` + the route path, kept as written | the app does not start | stated |
78
79
  | `""` | where the constructor put it | the constructor's prefix + the route path | unstated where the pack says so, otherwise stated |
79
80
  | `None`, `False`, `0` | where the constructor put it | the app does not start | unstated where the pack says so, otherwise unreadable |
80
- | a name or a call | whatever it evaluates to | whatever it evaluates to | unreadable |
81
+ | a name, a call, or an f-string the evaluator settles | whatever it evaluates to | whatever it evaluates to | stated, with the string it settles on |
82
+ | anything the evaluator cannot settle | whatever it evaluates to | whatever it evaluates to | unreadable |
81
83
 
82
84
  The two libraries differ on one property, and the pack says which way each one goes. flask-restx checks whether the path is truthy, so all four no-value spellings mean the same thing as writing nothing, at both places. FastAPI wants a string, so an empty string is an ordinary prefix that happens to add nothing, and the other three stop the app from starting.
83
85
 
@@ -101,7 +103,8 @@ FastAPI has no column here: its app has no prefix of its own, and the pack decla
101
103
  | `"/"` | the rest of the path, once the merge collapses the doubled slash | stated, and merging leaves nothing |
102
104
  | `""`, `None` | the rest of the path, no prefix | unstated |
103
105
  | `False`, `0` | the app does not start: Flask hands the value straight to `rstrip` | unstated |
104
- | a name or a call | whatever it evaluates to | unreadable |
106
+ | a name, a call, or an f-string the evaluator settles | whatever it evaluates to | stated, with the string it settles on |
107
+ | anything the evaluator cannot settle | whatever it evaluates to | unreadable |
105
108
 
106
109
  **At the `Api`** (`Api(bp, prefix=...)`):
107
110
 
@@ -111,7 +114,8 @@ FastAPI has no column here: its app has no prefix of its own, and the pack decla
111
114
  | `"/extra"` | the blueprint's prefix + `/extra` + the rest | stated |
112
115
  | `"/extra/"` | the same, concatenated as written, and Werkzeug answers the merged path | stated, and the composed path is merged |
113
116
  | `""`, `None`, `False`, `0` | the blueprint's prefix + the rest | unstated |
114
- | a name or a call | whatever it evaluates to | unreadable |
117
+ | a name, a call, or an f-string the evaluator settles | whatever it evaluates to | stated, with the string it settles on |
118
+ | anything the evaluator cannot settle | whatever it evaluates to | unreadable |
115
119
 
116
120
  ### A call that spreads a dictionary
117
121
 
@@ -149,11 +153,12 @@ A prefix written with a trailing slash leaves the composed path with two. Werkze
149
153
 
150
154
  ## What a body lowers to
151
155
 
152
- A route's own returns are what make it produce more than one transition, so the
153
- adapter lowers a function body into the statement form the shared path engine
154
- in `@suss/extractor` walks. That engine is generic over the language's own
155
- condition handle and never looks inside one, so the enumeration, the negation
156
- of an earlier arm, and the budget are all shared with TypeScript.
156
+ A route ends where it returns and where it raises, and each of those places is
157
+ what makes it produce more than one transition, so the adapter lowers a function
158
+ body into the statement form the shared path engine in `@suss/extractor` walks.
159
+ That engine is generic over the language's own condition handle and never looks
160
+ inside one, so the enumeration, the negation of an earlier arm, and the budget
161
+ are all shared with TypeScript.
157
162
 
158
163
  | Python | Lowers to |
159
164
  | --- | --- |
@@ -161,7 +166,7 @@ of an earlier arm, and the budget are all shared with TypeScript.
161
166
  | `while`, `for` | `loop` |
162
167
  | `try` / `except` / `finally` | `try`, with every except arm as the catch body |
163
168
  | `match` / `case` | `switch`, one group per case, `case _` as the default group |
164
- | `return`, `raise` | `exit`, which is what gives each return its own transition |
169
+ | `return`, `raise` | `exit`, which is what gives each of them its own transition |
165
170
  | `break`, `continue` | `exit`, which the engine uses for reachability rather than as an outcome |
166
171
  | anything else | `opaque` |
167
172
 
@@ -182,6 +187,45 @@ comes out as two transitions, the 404 gated on the opaque condition
182
187
  `not found` and the 200 gated on its negation, rather than as one claim with a
183
188
  guessed status.
184
189
 
190
+ ### A branch that ends by raising
191
+
192
+ Most frameworks let a route end the request by raising rather than by
193
+ returning, and the status the client receives is then whatever the raised
194
+ call was given. A pack says which of its library's callables do that, under
195
+ `responseStatusCalls`, and where each of them takes its status:
196
+
197
+ ```ts
198
+ responseStatusCalls: [
199
+ {
200
+ callee: "fastapi.HTTPException",
201
+ statusKeyword: "status_code",
202
+ statusArgument: 0,
203
+ },
204
+ ],
205
+ ```
206
+
207
+ `callee` is the name as the file imports it, module and name together, which
208
+ is what tells FastAPI's `HTTPException` apart from a project class of the same
209
+ name. A `raise` of one of these becomes a transition responding with that
210
+ status, and so does a bare call to one written as a statement of its own,
211
+ which is how Flask's `abort(404)` ends a request. So this handler:
212
+
213
+ ```python
214
+ @app.get("/items/{item_id}")
215
+ def show(item_id: int):
216
+ if item_id > 10:
217
+ raise HTTPException(status_code=404, detail="missing")
218
+ return {"id": item_id}
219
+ ```
220
+
221
+ comes out as a 404 under `item_id > 10` and a 200 under its negation.
222
+
223
+ A status the reading cannot resolve to a number is reported as the text it was
224
+ written as, so the transition says a status was set without claiming which. A
225
+ `raise` of anything the pack does not list is still a transition, with the
226
+ exception type on it and no status, since the framework is the only thing that
227
+ knows what it turns that exception into.
228
+
185
229
  ### Keying anything on a node
186
230
 
187
231
  tree-sitter hands back a fresh wrapper object every time a child is read, so
@@ -232,6 +276,15 @@ call that starts the chain is the one resolved, and the method the chain ends
232
276
  with tells a read from a write. A method a base and a subclass both declare
233
277
  gives two, and nothing is claimed.
234
278
 
279
+ A chain also matches when the name it starts on is declared, where the chain
280
+ is written, as one of the query types: a parameter annotated `db: Session`,
281
+ a local bound by `session = Session()` or `with Session() as session:`, or a
282
+ project function whose return annotation says `Session`. The type has to be
283
+ imported from the pattern's module, so a project class that happens to share
284
+ the name does not match. `recordsNothing` lists the methods on such a type
285
+ that touch no rows of their own: `execute` runs a statement whose own chain is
286
+ the read or write, and `close` manages the session.
287
+
235
288
  A query built from a function the library exports has no project method in
236
289
  between for its return to be read off, so a pack lists those by name and a call
237
290
  site importing one matches on that:
@@ -240,11 +293,15 @@ site importing one matches on that:
240
293
  queryFunctions: ["select", "insert", "update", "delete"]
241
294
  ```
242
295
 
243
- `fields` comes from the columns a query names, `User.id` in
244
- `select(User.id, User.email)`. `selector` comes from the keywords the chain
245
- picks rows by, `id` in `filter_by(id=user_id)`, gathered from every call in
246
- the chain rather than only the last. Raw SQL handed to `execute` is not read
247
- at all.
296
+ For one of these the function itself is the operation, so
297
+ `update(User).where(...).values(...)` is an update however it ends.
298
+
299
+ `fields` comes from the columns a query spells out, `User.id` in
300
+ `select(User.id, User.email)`, and from the keywords of a call the pack lists
301
+ under `valueMethods`, `name` in `.values(name="x")`. `selector` comes from
302
+ the keywords every other call in the chain picks rows by, `id` in
303
+ `filter_by(id=user_id)`. Raw SQL handed to `text` is read as its own effect,
304
+ with the kind and table taken from the statement.
248
305
 
249
306
  ## Where a mount is written
250
307
 
@@ -296,7 +353,7 @@ Taking the only mount it could follow would put the route at `/test`, where no r
296
353
 
297
354
  Both of those qualifiers matter. Only counting loops keeps the ordinary case working: two factories that each register their own routers by name are not competing, because neither one could have registered the other's. Only counting a loop in a *different* place keeps a factory working when it mixes an explicit registration with a loop, since both of those run together whenever that function runs.
298
355
 
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.
356
+ A module-level mount is never dropped this way. It runs whichever factory the app calls.
300
357
 
301
358
  ## What a file reads from the environment
302
359