@descryy/adapter-python 0.1.0 → 0.3.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/LICENSE +8 -0
- package/dist/adapter.d.ts +13 -20
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +91 -114
- package/dist/adapter.js.map +1 -1
- package/dist/client.d.ts +22 -41
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +35 -62
- package/dist/client.js.map +1 -1
- package/dist/extract.d.ts +31 -0
- package/dist/extract.d.ts.map +1 -1
- package/dist/extract.js +319 -48
- package/dist/extract.js.map +1 -1
- package/dist/extract.py +102 -0
- package/dist/graphql.d.ts +57 -123
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +62 -130
- package/dist/graphql.js.map +1 -1
- package/dist/module.d.ts +23 -42
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +26 -47
- package/dist/module.js.map +1 -1
- package/dist/orm.d.ts +31 -46
- package/dist/orm.d.ts.map +1 -1
- package/dist/orm.js +53 -89
- package/dist/orm.js.map +1 -1
- package/dist/pydantic.d.ts +76 -91
- package/dist/pydantic.d.ts.map +1 -1
- package/dist/pydantic.js +106 -99
- package/dist/pydantic.js.map +1 -1
- package/dist/routes.d.ts +46 -93
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +65 -129
- package/dist/routes.js.map +1 -1
- package/package.json +14 -5
- package/src/extract.py +102 -0
package/package.json
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descryy/adapter-python",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Python adapter. Parses with Python's own ast in one subprocess (DEC-062) \u2014 no grammar to keep in sync, and annotations arrive as source text.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/descryhq-wq/descry-adapters.git",
|
|
10
|
+
"directory": "packages/adapter-python"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/descryhq-wq/descry-adapters/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/descryhq-wq/descry-adapters#readme",
|
|
7
16
|
"engines": {
|
|
8
17
|
"node": ">=22.5"
|
|
9
18
|
},
|
|
@@ -26,9 +35,9 @@
|
|
|
26
35
|
"build:assets": "node -e \"require('fs').copyFileSync('src/extract.py','dist/extract.py')\""
|
|
27
36
|
},
|
|
28
37
|
"dependencies": {
|
|
29
|
-
"@descryy/adapter-common": "0.
|
|
30
|
-
"@descryy/ir": "0.
|
|
31
|
-
"@descryy/adapter-openapi": "0.
|
|
32
|
-
"@descryy/adapter-sql": "0.
|
|
38
|
+
"@descryy/adapter-common": "0.2.0",
|
|
39
|
+
"@descryy/ir": "0.5.0",
|
|
40
|
+
"@descryy/adapter-openapi": "0.3.0",
|
|
41
|
+
"@descryy/adapter-sql": "0.3.0"
|
|
33
42
|
}
|
|
34
43
|
}
|
package/src/extract.py
CHANGED
|
@@ -290,6 +290,17 @@ class Collector(ast.NodeVisitor):
|
|
|
290
290
|
# Module-level `name = ClassName()`, which is the only inference this
|
|
291
291
|
# file performs and the only one it can perform without a checker.
|
|
292
292
|
self.instantiations: list[dict] = []
|
|
293
|
+
# A local variable's *own* type annotation — `org: Organization | None
|
|
294
|
+
# = await get_org_for_user(...)` — as opposed to `instantiations`
|
|
295
|
+
# above, which only sees a bare constructor call on the right-hand
|
|
296
|
+
# side. The ground truth this exists for is exactly this shape: a
|
|
297
|
+
# local carrying its own annotation, assigned from a call whose
|
|
298
|
+
# return type nothing here resolves. Kept as the raw annotation
|
|
299
|
+
# string (not a single type name) so the Node side can run its own
|
|
300
|
+
# `typeNamesIn` + "exactly one candidate" discipline against
|
|
301
|
+
# `Optional[X]` / `X | None` / `Union[X, Y]`, the same as it already
|
|
302
|
+
# does for a parameter's own annotation.
|
|
303
|
+
self.local_annotations: list[dict] = []
|
|
293
304
|
# Module-level assignments that *might* be type aliases. Classified in
|
|
294
305
|
# `collect()` rather than here, because rule 4 asks where a constructor
|
|
295
306
|
# was imported from and the import table is only complete once the whole
|
|
@@ -682,6 +693,20 @@ class Collector(ast.NodeVisitor):
|
|
|
682
693
|
"line": node.lineno,
|
|
683
694
|
}
|
|
684
695
|
)
|
|
696
|
+
# A local variable's own annotation, inside a function (or
|
|
697
|
+
# nested closure) body — `org: Organization | None = await
|
|
698
|
+
# get_org_for_user(...)`. Module-level annotated assignments are
|
|
699
|
+
# already covered by `alias_candidates`/`_alembic_revision`; this
|
|
700
|
+
# is specifically the local-scope case those do not read.
|
|
701
|
+
if self._scope:
|
|
702
|
+
self.local_annotations.append(
|
|
703
|
+
{
|
|
704
|
+
"local": node.target.id,
|
|
705
|
+
"annotation": _annotation(node.annotation),
|
|
706
|
+
"scope": list(self._scope),
|
|
707
|
+
"line": node.lineno,
|
|
708
|
+
}
|
|
709
|
+
)
|
|
685
710
|
if not self._scope and node.value is not None:
|
|
686
711
|
self.alias_candidates.append(
|
|
687
712
|
{
|
|
@@ -1182,6 +1207,80 @@ def _classify_alias(candidate: dict, typing_names: set[str], bound: set[str]) ->
|
|
|
1182
1207
|
return None
|
|
1183
1208
|
|
|
1184
1209
|
|
|
1210
|
+
def _alembic_revision(tree: ast.Module) -> dict | None:
|
|
1211
|
+
"""`attrs.migrationRevision` — the revision/down_revision half `_migration_ops`
|
|
1212
|
+
does not read.
|
|
1213
|
+
|
|
1214
|
+
Alembic's generator writes exactly this pair at module scope in every
|
|
1215
|
+
script it produces: `revision = "..."` and `down_revision = "..." | None`.
|
|
1216
|
+
Gated on **both names being assigned at module level**, not just one —
|
|
1217
|
+
a bare `revision = ...` is not rare on its own (a git SHA, an API version)
|
|
1218
|
+
and is not enough by itself to claim this file is a migration; the pair is.
|
|
1219
|
+
|
|
1220
|
+
`down_revision` carries three shapes Alembic itself writes: a single
|
|
1221
|
+
string (one parent), the literal `None` (a root migration — a real,
|
|
1222
|
+
meaningful empty parent, not an unread one), or a tuple/list of strings
|
|
1223
|
+
(a merge revision, several parents). Anything else written there — a
|
|
1224
|
+
computed expression, a name — is reported as the string `"unknown"`
|
|
1225
|
+
rather than guessed at: the same "a name-matched guess is a false claim"
|
|
1226
|
+
rule `descry-core`'s own regex-based migration reader applies to this
|
|
1227
|
+
exact line from the outside (`sources/migrations/dialects.ts`).
|
|
1228
|
+
"""
|
|
1229
|
+
|
|
1230
|
+
def string_of(node: ast.AST) -> str | None:
|
|
1231
|
+
return node.value if isinstance(node, ast.Constant) and isinstance(node.value, str) else None
|
|
1232
|
+
|
|
1233
|
+
revision: str | None = None
|
|
1234
|
+
has_down_revision = False
|
|
1235
|
+
down_revision: str | list[str] | None = None
|
|
1236
|
+
|
|
1237
|
+
for node in tree.body:
|
|
1238
|
+
target: ast.AST | None
|
|
1239
|
+
value: ast.AST | None
|
|
1240
|
+
if isinstance(node, ast.Assign) and len(node.targets) == 1:
|
|
1241
|
+
target = node.targets[0]
|
|
1242
|
+
value = node.value
|
|
1243
|
+
elif isinstance(node, ast.AnnAssign):
|
|
1244
|
+
# `revision: str = "..."` — a type-annotated assignment. Alembic's
|
|
1245
|
+
# newer templates and mypy-strict repos write the pair this way;
|
|
1246
|
+
# it is semantically the same assignment, just with an annotation
|
|
1247
|
+
# attached. `node.value` is `None` for a bare annotation with no
|
|
1248
|
+
# value (`down_revision: str` alone) — that assigns nothing, so it
|
|
1249
|
+
# must not be treated as `down_revision = None` (a root migration
|
|
1250
|
+
# is a real, distinct fact). Skip the node entirely in that case.
|
|
1251
|
+
if node.value is None:
|
|
1252
|
+
continue
|
|
1253
|
+
target = node.target
|
|
1254
|
+
value = node.value
|
|
1255
|
+
else:
|
|
1256
|
+
continue
|
|
1257
|
+
if not isinstance(target, ast.Name):
|
|
1258
|
+
continue
|
|
1259
|
+
|
|
1260
|
+
if target.id == "revision":
|
|
1261
|
+
revision = string_of(value)
|
|
1262
|
+
continue
|
|
1263
|
+
|
|
1264
|
+
if target.id == "down_revision":
|
|
1265
|
+
has_down_revision = True
|
|
1266
|
+
if isinstance(value, ast.Constant) and value.value is None:
|
|
1267
|
+
down_revision = None
|
|
1268
|
+
else:
|
|
1269
|
+
single = string_of(value)
|
|
1270
|
+
if single is not None:
|
|
1271
|
+
down_revision = single
|
|
1272
|
+
elif isinstance(value, (ast.Tuple, ast.List)):
|
|
1273
|
+
ids = [string_of(elt) for elt in value.elts]
|
|
1274
|
+
down_revision = ids if len(ids) > 0 and all(i is not None for i in ids) else "unknown"
|
|
1275
|
+
else:
|
|
1276
|
+
down_revision = "unknown"
|
|
1277
|
+
|
|
1278
|
+
if revision is None or not has_down_revision:
|
|
1279
|
+
return None
|
|
1280
|
+
|
|
1281
|
+
return {"revision": revision, "downRevision": down_revision}
|
|
1282
|
+
|
|
1283
|
+
|
|
1185
1284
|
def collect(path: str, source: str, deep_call_modules: frozenset[str] = frozenset()) -> dict:
|
|
1186
1285
|
tree = ast.parse(source, filename=path)
|
|
1187
1286
|
collector = Collector(path)
|
|
@@ -1235,12 +1334,14 @@ def collect(path: str, source: str, deep_call_modules: frozenset[str] = frozense
|
|
|
1235
1334
|
|
|
1236
1335
|
env_reads, env_dynamic_reads = _env_reads(tree, collector.imports)
|
|
1237
1336
|
migration_ops = _migration_ops(tree, collector.imports)
|
|
1337
|
+
migration_revision = _alembic_revision(tree)
|
|
1238
1338
|
|
|
1239
1339
|
return {
|
|
1240
1340
|
"declarations": collector.declarations,
|
|
1241
1341
|
"imports": collector.imports,
|
|
1242
1342
|
"references": collector.references,
|
|
1243
1343
|
"instantiations": collector.instantiations,
|
|
1344
|
+
"localAnnotations": collector.local_annotations,
|
|
1244
1345
|
"valueRefs": collector.value_refs,
|
|
1245
1346
|
"aliases": aliases,
|
|
1246
1347
|
"calls": collector.calls,
|
|
@@ -1250,6 +1351,7 @@ def collect(path: str, source: str, deep_call_modules: frozenset[str] = frozense
|
|
|
1250
1351
|
"envReads": env_reads,
|
|
1251
1352
|
"envDynamicReads": env_dynamic_reads,
|
|
1252
1353
|
"migrationOps": migration_ops,
|
|
1354
|
+
"migrationRevision": migration_revision,
|
|
1253
1355
|
}
|
|
1254
1356
|
|
|
1255
1357
|
|