@suss/adapter-python 0.3.2-0 → 0.5.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
@@ -101,6 +101,105 @@ 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
+ A query built from a function the library exports has no project method in
190
+ between for its return to be read off, so a pack lists those by name and a call
191
+ site importing one matches on that:
192
+
193
+ ```ts
194
+ queryFunctions: ["select", "insert", "update", "delete"]
195
+ ```
196
+
197
+ `fields` comes from the columns a query names, `User.id` in
198
+ `select(User.id, User.email)`. `selector` comes from the keywords the chain
199
+ picks rows by, `id` in `filter_by(id=user_id)`, gathered from every call in
200
+ the chain rather than only the last. Raw SQL handed to `execute` is not read
201
+ at all.
202
+
104
203
  ## Where a mount is written
105
204
 
106
205
  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: