@kernlang/python 3.5.6-canary.199.1.c927d232 → 3.5.6-canary.202.1.31706b95
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/dist/adapters/django.d.ts +49 -0
- package/dist/adapters/django.js +151 -0
- package/dist/adapters/django.js.map +1 -0
- package/dist/adapters/fastapi.d.ts +43 -0
- package/dist/adapters/fastapi.js +139 -0
- package/dist/adapters/fastapi.js.map +1 -0
- package/dist/codegen-body-python.d.ts +51 -2
- package/dist/codegen-body-python.js +281 -26
- package/dist/codegen-body-python.js.map +1 -1
- package/dist/codegen-python.d.ts +1 -0
- package/dist/codegen-python.js +1 -1
- package/dist/codegen-python.js.map +1 -1
- package/dist/core/emit-imports.d.ts +11 -0
- package/dist/core/emit-imports.js +166 -0
- package/dist/core/emit-imports.js.map +1 -0
- package/dist/core/emit-models.d.ts +14 -0
- package/dist/core/emit-models.js +86 -0
- package/dist/core/emit-models.js.map +1 -0
- package/dist/core/expr/helpers.d.ts +5 -0
- package/dist/core/expr/helpers.js +62 -0
- package/dist/core/expr/helpers.js.map +1 -0
- package/dist/core/expr/index.d.ts +9 -0
- package/dist/core/expr/index.js +2046 -0
- package/dist/core/expr/index.js.map +1 -0
- package/dist/core/fence-diagnostics.d.ts +17 -0
- package/dist/core/fence-diagnostics.js +86 -0
- package/dist/core/fence-diagnostics.js.map +1 -0
- package/dist/core/handlers/index.d.ts +74 -0
- package/dist/core/handlers/index.js +462 -0
- package/dist/core/handlers/index.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/type-mapper.d.ts +4 -0
- package/dist/core/type-mapper.js +8 -0
- package/dist/core/type-mapper.js.map +1 -0
- package/dist/fastapi-portable.d.ts +7 -1
- package/dist/fastapi-portable.js +97 -74
- package/dist/fastapi-portable.js.map +1 -1
- package/dist/fastapi-response.d.ts +0 -4
- package/dist/fastapi-response.js +1 -1868
- package/dist/fastapi-response.js.map +1 -1
- package/dist/fastapi-route.js +24 -3
- package/dist/fastapi-route.js.map +1 -1
- package/dist/fastapi-utils.d.ts +2 -1
- package/dist/fastapi-utils.js +2 -58
- package/dist/fastapi-utils.js.map +1 -1
- package/dist/generators/data.d.ts +3 -1
- package/dist/generators/data.js +28 -1
- package/dist/generators/data.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/ir-semantics/python-leg.js +5 -0
- package/dist/ir-semantics/python-leg.js.map +1 -1
- package/dist/targets/python.d.ts +32 -0
- package/dist/targets/python.js +176 -0
- package/dist/targets/python.js.map +1 -0
- package/dist/transpiler-fastapi.js +23 -66
- package/dist/transpiler-fastapi.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Django adapter — revised contract (post nero red-team).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `PurePythonHandler` values as Django views + URL conf. Same
|
|
5
|
+
* marshalling-only pattern as the FastAPI adapter — derive/guard/respond
|
|
6
|
+
* live in the pure handler; the adapter ONLY translates Django's
|
|
7
|
+
* `HttpRequest` into a `PureRequest` dict and back.
|
|
8
|
+
*
|
|
9
|
+
* Adapter responsibilities (the ONLY things it does):
|
|
10
|
+
* 1. urls.py: one `path()` per route, mapping the (KERN `:param` → Django
|
|
11
|
+
* `<type:name>`) converted path to the view function.
|
|
12
|
+
* 2. views.py per route: build PureRequest dict from `request.method`,
|
|
13
|
+
* view-fn kwargs (path params), `request.GET` (query), `json.loads(
|
|
14
|
+
* request.body)` (body when content-type is JSON; else raw bytes),
|
|
15
|
+
* `dict(request.headers)`. Call `handle_<fnName>(pure_request)`.
|
|
16
|
+
* Return `JsonResponse(body, status=status, headers=extra_headers or {})`.
|
|
17
|
+
* 3. settings.py: minimal — `DEBUG`, `SECRET_KEY` (env), `INSTALLED_APPS=['django.contrib.contenttypes','django.contrib.auth']`
|
|
18
|
+
* (the bare minimum Django requires), `ROOT_URLCONF`, `DATABASES={}`,
|
|
19
|
+
* `ALLOWED_HOSTS=['*']`.
|
|
20
|
+
* 4. manage.py: standard Django CLI entry point.
|
|
21
|
+
*
|
|
22
|
+
* Acceptance (Phase 3b smoke + Wave 3 end-to-end):
|
|
23
|
+
* - Synthetic PureRequest fixture (hand-built in the smoke, NOT from
|
|
24
|
+
* Django) → pure handler returns expected (status, body).
|
|
25
|
+
* - Django smoke: transpile a real route, generate the Django project,
|
|
26
|
+
* run `python manage.py runserver --noreload <port>`, curl the route,
|
|
27
|
+
* assert response. CRITICAL: use `--noreload` so the SIGTERM hits the
|
|
28
|
+
* real server, not a parent autoreloader (the equivalent of #4's
|
|
29
|
+
* `go run .` orphan-on-SIGTERM hang).
|
|
30
|
+
*
|
|
31
|
+
* Django is installed globally (`django 6.0.5`) on this dev environment; CI
|
|
32
|
+
* will need `pip install django` in the workflow that runs the smoke.
|
|
33
|
+
*/
|
|
34
|
+
import type { PurePythonHandler } from '../core/handlers/index.js';
|
|
35
|
+
export interface DjangoAdapterArtifacts {
|
|
36
|
+
/** Python source for urls.py (URL conf with `path('users/', views.handle_get_users)` lines). */
|
|
37
|
+
urlsPy: string;
|
|
38
|
+
/** Python source for views.py (view functions wrapping each PurePythonHandler). */
|
|
39
|
+
viewsPy: string;
|
|
40
|
+
/** Python source for settings.py (minimal Django settings — INSTALLED_APPS, ROOT_URLCONF, DEBUG). */
|
|
41
|
+
settingsPy: string;
|
|
42
|
+
/** Python source for manage.py (Django CLI entry point). */
|
|
43
|
+
managePy: string;
|
|
44
|
+
/** Python source for the pure-handlers module the adapter views import from. Phase 3b's smoke uses a synthetic version; in production the python target writes the real one. */
|
|
45
|
+
pureHandlersPy: string;
|
|
46
|
+
/** Imports the ADAPTER itself needs at the top of views.py. */
|
|
47
|
+
imports: Set<string>;
|
|
48
|
+
}
|
|
49
|
+
export declare function emitDjangoAdapter(handlers: PurePythonHandler[]): DjangoAdapterArtifacts;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Django adapter — revised contract (post nero red-team).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `PurePythonHandler` values as Django views + URL conf. Same
|
|
5
|
+
* marshalling-only pattern as the FastAPI adapter — derive/guard/respond
|
|
6
|
+
* live in the pure handler; the adapter ONLY translates Django's
|
|
7
|
+
* `HttpRequest` into a `PureRequest` dict and back.
|
|
8
|
+
*
|
|
9
|
+
* Adapter responsibilities (the ONLY things it does):
|
|
10
|
+
* 1. urls.py: one `path()` per route, mapping the (KERN `:param` → Django
|
|
11
|
+
* `<type:name>`) converted path to the view function.
|
|
12
|
+
* 2. views.py per route: build PureRequest dict from `request.method`,
|
|
13
|
+
* view-fn kwargs (path params), `request.GET` (query), `json.loads(
|
|
14
|
+
* request.body)` (body when content-type is JSON; else raw bytes),
|
|
15
|
+
* `dict(request.headers)`. Call `handle_<fnName>(pure_request)`.
|
|
16
|
+
* Return `JsonResponse(body, status=status, headers=extra_headers or {})`.
|
|
17
|
+
* 3. settings.py: minimal — `DEBUG`, `SECRET_KEY` (env), `INSTALLED_APPS=['django.contrib.contenttypes','django.contrib.auth']`
|
|
18
|
+
* (the bare minimum Django requires), `ROOT_URLCONF`, `DATABASES={}`,
|
|
19
|
+
* `ALLOWED_HOSTS=['*']`.
|
|
20
|
+
* 4. manage.py: standard Django CLI entry point.
|
|
21
|
+
*
|
|
22
|
+
* Acceptance (Phase 3b smoke + Wave 3 end-to-end):
|
|
23
|
+
* - Synthetic PureRequest fixture (hand-built in the smoke, NOT from
|
|
24
|
+
* Django) → pure handler returns expected (status, body).
|
|
25
|
+
* - Django smoke: transpile a real route, generate the Django project,
|
|
26
|
+
* run `python manage.py runserver --noreload <port>`, curl the route,
|
|
27
|
+
* assert response. CRITICAL: use `--noreload` so the SIGTERM hits the
|
|
28
|
+
* real server, not a parent autoreloader (the equivalent of #4's
|
|
29
|
+
* `go run .` orphan-on-SIGTERM hang).
|
|
30
|
+
*
|
|
31
|
+
* Django is installed globally (`django 6.0.5`) on this dev environment; CI
|
|
32
|
+
* will need `pip install django` in the workflow that runs the smoke.
|
|
33
|
+
*/
|
|
34
|
+
export function emitDjangoAdapter(handlers) {
|
|
35
|
+
const urlPaths = handlers.map((h) => {
|
|
36
|
+
const cleanPath = h.path.startsWith('/') ? h.path.slice(1) : h.path;
|
|
37
|
+
const segments = cleanPath.split('/');
|
|
38
|
+
const hasParams = segments.some((s) => s.startsWith(':'));
|
|
39
|
+
const mappedSegments = segments.map((segment) => {
|
|
40
|
+
if (segment.startsWith(':')) {
|
|
41
|
+
const paramName = segment.slice(1);
|
|
42
|
+
const paramType = h.pathParamTypes?.[paramName] || 'str';
|
|
43
|
+
return `<${paramType}:${paramName}>`;
|
|
44
|
+
}
|
|
45
|
+
return segment;
|
|
46
|
+
});
|
|
47
|
+
const djangoPath = mappedSegments.join('/');
|
|
48
|
+
if (hasParams) {
|
|
49
|
+
return ` path("${djangoPath}", views.${h.fnName}_route, name="${h.fnName}"),`;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
return ` path("${djangoPath}", views.${h.fnName}_route),`;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const urlsPy = `from django.urls import path
|
|
56
|
+
try:
|
|
57
|
+
from . import views
|
|
58
|
+
except ImportError:
|
|
59
|
+
import views
|
|
60
|
+
|
|
61
|
+
urlpatterns = [
|
|
62
|
+
${urlPaths.join('\n')}
|
|
63
|
+
]
|
|
64
|
+
`;
|
|
65
|
+
const pureImportsList = Array.from(new Set(handlers.map((h) => h.fnName)));
|
|
66
|
+
const viewsImports = `import json
|
|
67
|
+
from django.http import JsonResponse, HttpRequest
|
|
68
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
69
|
+
from django.views.decorators.http import require_http_methods
|
|
70
|
+
try:
|
|
71
|
+
from .pure_handlers import ${pureImportsList.join(', ')}
|
|
72
|
+
except ImportError:
|
|
73
|
+
from pure_handlers import ${pureImportsList.join(', ')}`;
|
|
74
|
+
const viewFunctions = handlers
|
|
75
|
+
.map((h) => {
|
|
76
|
+
const method = h.method.toUpperCase();
|
|
77
|
+
const responseHeadersJson = JSON.stringify(h.responseHeaders ?? {});
|
|
78
|
+
return `@csrf_exempt
|
|
79
|
+
@require_http_methods(["${method}"])
|
|
80
|
+
def ${h.fnName}_route(request: HttpRequest, **path_kwargs):
|
|
81
|
+
try:
|
|
82
|
+
body = json.loads(request.body) if request.body else {}
|
|
83
|
+
except json.JSONDecodeError:
|
|
84
|
+
body = {}
|
|
85
|
+
pure_request = {
|
|
86
|
+
"method": request.method,
|
|
87
|
+
"path_params": path_kwargs,
|
|
88
|
+
"query": {k: (request.GET.getlist(k)[0] if len(request.GET.getlist(k)) == 1 else request.GET.getlist(k)) for k in request.GET},
|
|
89
|
+
"body": body,
|
|
90
|
+
"headers": {k.lower(): v for k, v in request.headers.items()},
|
|
91
|
+
"user": getattr(request, "user", None),
|
|
92
|
+
}
|
|
93
|
+
result = ${h.fnName}(pure_request)
|
|
94
|
+
status, body_out, *rest = result if isinstance(result, tuple) else (200, result)
|
|
95
|
+
extra_headers = rest[0] if rest else {}
|
|
96
|
+
merged_headers = {**${responseHeadersJson}, **extra_headers}
|
|
97
|
+
resp = JsonResponse(body_out, status=status, safe=False)
|
|
98
|
+
for k, v in merged_headers.items():
|
|
99
|
+
resp[k] = v
|
|
100
|
+
return resp`;
|
|
101
|
+
})
|
|
102
|
+
.join('\n\n');
|
|
103
|
+
const viewsPy = `${viewsImports}
|
|
104
|
+
|
|
105
|
+
${viewFunctions}
|
|
106
|
+
`;
|
|
107
|
+
const settingsPy = `import os
|
|
108
|
+
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "kern-dev-secret-not-for-prod")
|
|
109
|
+
DEBUG = True
|
|
110
|
+
ALLOWED_HOSTS = ["*"]
|
|
111
|
+
INSTALLED_APPS = ["django.contrib.contenttypes", "django.contrib.auth"]
|
|
112
|
+
MIDDLEWARE = []
|
|
113
|
+
ROOT_URLCONF = "urls"
|
|
114
|
+
DATABASES = {}
|
|
115
|
+
USE_TZ = True
|
|
116
|
+
TIME_ZONE = "UTC"
|
|
117
|
+
`;
|
|
118
|
+
const managePy = `#!/usr/bin/env python
|
|
119
|
+
import os, sys
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
|
|
122
|
+
from django.core.management import execute_from_command_line
|
|
123
|
+
execute_from_command_line(sys.argv)
|
|
124
|
+
`;
|
|
125
|
+
const handlerImportsSet = new Set();
|
|
126
|
+
for (const h of handlers) {
|
|
127
|
+
if (h.imports) {
|
|
128
|
+
for (const imp of h.imports) {
|
|
129
|
+
handlerImportsSet.add(imp);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const pureHandlersImportsStr = Array.from(handlerImportsSet).sort().join('\n');
|
|
134
|
+
const pureHandlersCode = handlers.map((h) => `${h.signature}\n${h.bodyLines.join('\n')}`).join('\n\n');
|
|
135
|
+
const pureHandlersPy = pureHandlersImportsStr ? `${pureHandlersImportsStr}\n\n${pureHandlersCode}` : pureHandlersCode;
|
|
136
|
+
const imports = new Set([
|
|
137
|
+
'import json',
|
|
138
|
+
'from django.http import JsonResponse, HttpRequest',
|
|
139
|
+
'from django.views.decorators.csrf import csrf_exempt',
|
|
140
|
+
'from django.views.decorators.http import require_http_methods',
|
|
141
|
+
]);
|
|
142
|
+
return {
|
|
143
|
+
urlsPy,
|
|
144
|
+
viewsPy,
|
|
145
|
+
settingsPy,
|
|
146
|
+
managePy,
|
|
147
|
+
pureHandlersPy,
|
|
148
|
+
imports,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=django.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"django.js","sourceRoot":"","sources":["../../src/adapters/django.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FastAPI adapter — revised contract (post nero red-team).
|
|
3
|
+
*
|
|
4
|
+
* Takes a list of framework-agnostic `PurePythonHandler` values and emits a
|
|
5
|
+
* FastAPI app skin that wraps each handler in a decorated endpoint. The
|
|
6
|
+
* adapter does ONLY marshalling — it builds a `PureRequest` dict from
|
|
7
|
+
* FastAPI's `Request` + path/query/body, calls the pure handler, unpacks
|
|
8
|
+
* the `PureResponse` tuple, and returns a `JSONResponse`. ROUTE LOWERING
|
|
9
|
+
* STAYS IN THE PURE HANDLER (the adapter never re-implements derive/guard/
|
|
10
|
+
* respond — that's the whole point of decoupling).
|
|
11
|
+
*
|
|
12
|
+
* Adapter responsibilities (the ONLY things it does):
|
|
13
|
+
* 1. Wire path params from FastAPI's typed signature into PureRequest.path_params
|
|
14
|
+
* (using `PurePythonHandler.pathParamTypes` for coercion).
|
|
15
|
+
* 2. Wire query params from `request.query_params` into PureRequest.query
|
|
16
|
+
* (using `PurePythonHandler.queryParamTypes` for coercion).
|
|
17
|
+
* 3. Wire body — if `validatesSchema` is set, attach the existing Pydantic
|
|
18
|
+
* model as a FastAPI body param; pass `.model_dump()` into the dict.
|
|
19
|
+
* Else: `await request.json()` if content-type is JSON, else raw bytes.
|
|
20
|
+
* 4. Build the PureRequest dict.
|
|
21
|
+
* 5. Call the pure handler `fnName(pure_request)`.
|
|
22
|
+
* 6. Unpack `(status, body[, headers])` → `JSONResponse(content=body, status_code=status, headers=headers or {})`.
|
|
23
|
+
*
|
|
24
|
+
* Acceptance (Phase 3a smoke + Wave 3 end-to-end):
|
|
25
|
+
* - Synthetic PureRequest fixture (hand-built in the smoke, not from any
|
|
26
|
+
* framework) → pure handler returns expected (status, body).
|
|
27
|
+
* - The existing fastapi conformance suite (194/194 fixtures) passes when
|
|
28
|
+
* routed through `pure-python + this adapter` pipeline (Wave 3).
|
|
29
|
+
*
|
|
30
|
+
* The current monolithic `transpiler-fastapi.ts` stays as the default until
|
|
31
|
+
* Phase 3a + Phase 2 both land and a follow-up flips the wiring. This phase
|
|
32
|
+
* only ADDS the adapter; it does not remove the monolith.
|
|
33
|
+
*/
|
|
34
|
+
import type { PurePythonHandler } from '../core/handlers/index.js';
|
|
35
|
+
export interface FastAPIAdapterArtifacts {
|
|
36
|
+
/** Python source for the FastAPI app file (imports, `app = FastAPI()`, decorated endpoints). */
|
|
37
|
+
appPy: string;
|
|
38
|
+
/** Python source for the pure-handlers module (re-emitted as a sibling .py file the adapter imports). Phase 3a's smoke uses a synthetic version of this; in production the python target writes the real one. */
|
|
39
|
+
pureHandlersPy: string;
|
|
40
|
+
/** Imports the ADAPTER itself needs at the top of appPy. */
|
|
41
|
+
imports: Set<string>;
|
|
42
|
+
}
|
|
43
|
+
export declare function emitFastAPIAdapter(handlers: PurePythonHandler[]): FastAPIAdapterArtifacts;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FastAPI adapter — revised contract (post nero red-team).
|
|
3
|
+
*
|
|
4
|
+
* Takes a list of framework-agnostic `PurePythonHandler` values and emits a
|
|
5
|
+
* FastAPI app skin that wraps each handler in a decorated endpoint. The
|
|
6
|
+
* adapter does ONLY marshalling — it builds a `PureRequest` dict from
|
|
7
|
+
* FastAPI's `Request` + path/query/body, calls the pure handler, unpacks
|
|
8
|
+
* the `PureResponse` tuple, and returns a `JSONResponse`. ROUTE LOWERING
|
|
9
|
+
* STAYS IN THE PURE HANDLER (the adapter never re-implements derive/guard/
|
|
10
|
+
* respond — that's the whole point of decoupling).
|
|
11
|
+
*
|
|
12
|
+
* Adapter responsibilities (the ONLY things it does):
|
|
13
|
+
* 1. Wire path params from FastAPI's typed signature into PureRequest.path_params
|
|
14
|
+
* (using `PurePythonHandler.pathParamTypes` for coercion).
|
|
15
|
+
* 2. Wire query params from `request.query_params` into PureRequest.query
|
|
16
|
+
* (using `PurePythonHandler.queryParamTypes` for coercion).
|
|
17
|
+
* 3. Wire body — if `validatesSchema` is set, attach the existing Pydantic
|
|
18
|
+
* model as a FastAPI body param; pass `.model_dump()` into the dict.
|
|
19
|
+
* Else: `await request.json()` if content-type is JSON, else raw bytes.
|
|
20
|
+
* 4. Build the PureRequest dict.
|
|
21
|
+
* 5. Call the pure handler `fnName(pure_request)`.
|
|
22
|
+
* 6. Unpack `(status, body[, headers])` → `JSONResponse(content=body, status_code=status, headers=headers or {})`.
|
|
23
|
+
*
|
|
24
|
+
* Acceptance (Phase 3a smoke + Wave 3 end-to-end):
|
|
25
|
+
* - Synthetic PureRequest fixture (hand-built in the smoke, not from any
|
|
26
|
+
* framework) → pure handler returns expected (status, body).
|
|
27
|
+
* - The existing fastapi conformance suite (194/194 fixtures) passes when
|
|
28
|
+
* routed through `pure-python + this adapter` pipeline (Wave 3).
|
|
29
|
+
*
|
|
30
|
+
* The current monolithic `transpiler-fastapi.ts` stays as the default until
|
|
31
|
+
* Phase 3a + Phase 2 both land and a follow-up flips the wiring. This phase
|
|
32
|
+
* only ADDS the adapter; it does not remove the monolith.
|
|
33
|
+
*/
|
|
34
|
+
export function emitFastAPIAdapter(handlers) {
|
|
35
|
+
const imports = new Set([
|
|
36
|
+
'from fastapi import FastAPI, Request',
|
|
37
|
+
'from fastapi.responses import JSONResponse',
|
|
38
|
+
]);
|
|
39
|
+
const appPyLines = [];
|
|
40
|
+
// Add standard imports for FastAPI
|
|
41
|
+
appPyLines.push('from fastapi import FastAPI, Request');
|
|
42
|
+
appPyLines.push('from fastapi.responses import JSONResponse');
|
|
43
|
+
// Import each pure handler and its validatesSchema model (if any) from the pure_handlers module
|
|
44
|
+
// Use try-except to support both absolute and relative imports across different runtime environments
|
|
45
|
+
for (const handler of handlers) {
|
|
46
|
+
appPyLines.push('try:');
|
|
47
|
+
appPyLines.push(` from .pure_handlers import ${handler.fnName}`);
|
|
48
|
+
appPyLines.push('except ImportError:');
|
|
49
|
+
appPyLines.push(` from pure_handlers import ${handler.fnName}`);
|
|
50
|
+
if (handler.validatesSchema) {
|
|
51
|
+
appPyLines.push('try:');
|
|
52
|
+
appPyLines.push(` from .pure_handlers import ${handler.validatesSchema}`);
|
|
53
|
+
appPyLines.push('except ImportError:');
|
|
54
|
+
appPyLines.push(` from pure_handlers import ${handler.validatesSchema}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
appPyLines.push('');
|
|
58
|
+
appPyLines.push('app = FastAPI()');
|
|
59
|
+
appPyLines.push('');
|
|
60
|
+
function mapPythonType(typeStr) {
|
|
61
|
+
if (!typeStr)
|
|
62
|
+
return 'str';
|
|
63
|
+
const t = typeStr.trim().toLowerCase();
|
|
64
|
+
if (t === 'string')
|
|
65
|
+
return 'str';
|
|
66
|
+
if (t === 'number')
|
|
67
|
+
return 'float';
|
|
68
|
+
if (t === 'boolean')
|
|
69
|
+
return 'bool';
|
|
70
|
+
if (t === 'integer')
|
|
71
|
+
return 'int';
|
|
72
|
+
return typeStr;
|
|
73
|
+
}
|
|
74
|
+
for (const handler of handlers) {
|
|
75
|
+
// Convert path param style from KERN (:param) to FastAPI ({param})
|
|
76
|
+
const pathWithFastapiBraces = handler.path.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');
|
|
77
|
+
// Extract path param names
|
|
78
|
+
const paramMatches = handler.path.match(/:([a-zA-Z0-9_]+)/g) || [];
|
|
79
|
+
const paramNames = paramMatches.map((m) => m.slice(1));
|
|
80
|
+
// Map path params to typed parameters
|
|
81
|
+
const typedPathParams = paramNames.map((name) => {
|
|
82
|
+
const type = handler.pathParamTypes[name] || 'str';
|
|
83
|
+
return `${name}: ${mapPythonType(type)}`;
|
|
84
|
+
});
|
|
85
|
+
const bodyPydanticParamOrNone = handler.validatesSchema ? `body_pydantic_param: ${handler.validatesSchema}` : '';
|
|
86
|
+
const routeArgs = [];
|
|
87
|
+
for (const p of typedPathParams) {
|
|
88
|
+
routeArgs.push(p);
|
|
89
|
+
}
|
|
90
|
+
if (bodyPydanticParamOrNone) {
|
|
91
|
+
routeArgs.push(bodyPydanticParamOrNone);
|
|
92
|
+
}
|
|
93
|
+
routeArgs.push('request: Request');
|
|
94
|
+
const routeArgsStr = routeArgs.join(', ');
|
|
95
|
+
// Construct path params dict
|
|
96
|
+
const pathParamsEntries = paramNames.map((name) => `"${name}": ${name}`).join(', ');
|
|
97
|
+
const pathParamsStr = `{${pathParamsEntries}}`;
|
|
98
|
+
const responseHeadersLiteral = JSON.stringify(handler.responseHeaders);
|
|
99
|
+
const methodLower = handler.method.toLowerCase();
|
|
100
|
+
const methodUpper = handler.method.toUpperCase();
|
|
101
|
+
appPyLines.push(`@app.${methodLower}("${pathWithFastapiBraces}")`);
|
|
102
|
+
appPyLines.push(`async def ${handler.fnName}_route(${routeArgsStr}):`);
|
|
103
|
+
if (!handler.validatesSchema) {
|
|
104
|
+
appPyLines.push(' body_pydantic_param = None');
|
|
105
|
+
}
|
|
106
|
+
appPyLines.push(' pure_request = {');
|
|
107
|
+
appPyLines.push(` "method": "${methodUpper}",`);
|
|
108
|
+
appPyLines.push(` "path_params": ${pathParamsStr},`);
|
|
109
|
+
appPyLines.push(' "query": {k: (request.query_params.getlist(k)[0] if len(request.query_params.getlist(k)) == 1 else request.query_params.getlist(k)) for k in request.query_params.keys()},');
|
|
110
|
+
appPyLines.push(' "body": (body_pydantic_param.model_dump() if body_pydantic_param is not None else (await request.json() if "application/json" in request.headers.get("content-type", "") else (await request.body()))),');
|
|
111
|
+
appPyLines.push(' "headers": {k.lower(): v for k, v in request.headers.items()},');
|
|
112
|
+
appPyLines.push(' "user": getattr(request.state, "user", None),');
|
|
113
|
+
appPyLines.push(' }');
|
|
114
|
+
appPyLines.push(` result = ${handler.fnName}(pure_request)`);
|
|
115
|
+
appPyLines.push(' status, body, *rest = result if isinstance(result, tuple) else (200, result)');
|
|
116
|
+
appPyLines.push(' extra_headers = rest[0] if rest else {}');
|
|
117
|
+
appPyLines.push(` merged_headers = {**${responseHeadersLiteral}, **extra_headers}`);
|
|
118
|
+
appPyLines.push(' return JSONResponse(content=body, status_code=status, headers=merged_headers or None)');
|
|
119
|
+
appPyLines.push('');
|
|
120
|
+
}
|
|
121
|
+
// Concatenate all handler code to build pureHandlersPy, including their combined imports
|
|
122
|
+
const allPureImports = new Set();
|
|
123
|
+
for (const handler of handlers) {
|
|
124
|
+
if (handler.imports) {
|
|
125
|
+
for (const imp of handler.imports) {
|
|
126
|
+
allPureImports.add(imp);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const sortedPureImports = Array.from(allPureImports).sort();
|
|
131
|
+
const pureImportsStr = sortedPureImports.length > 0 ? `${sortedPureImports.join('\n')}\n\n` : '';
|
|
132
|
+
const pureHandlersPy = `${pureImportsStr + handlers.map((h) => `${h.signature}\n${h.bodyLines.join('\n')}`).join('\n\n')}\n`;
|
|
133
|
+
return {
|
|
134
|
+
appPy: appPyLines.join('\n'),
|
|
135
|
+
pureHandlersPy,
|
|
136
|
+
imports,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=fastapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastapi.js","sourceRoot":"","sources":["../../src/adapters/fastapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAaH,MAAM,UAAU,kBAAkB,CAAC,QAA6B;IAC9D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS;QAC9B,sCAAsC;QACtC,4CAA4C;KAC7C,CAAC,CAAC;IAEH,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,mCAAmC;IACnC,UAAU,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IACxD,UAAU,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAE9D,gGAAgG;IAChG,qGAAqG;IACrG,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxB,UAAU,CAAC,IAAI,CAAC,kCAAkC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACpE,UAAU,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACvC,UAAU,CAAC,IAAI,CAAC,iCAAiC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAEnE,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACxB,UAAU,CAAC,IAAI,CAAC,kCAAkC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;YAC7E,UAAU,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACvC,UAAU,CAAC,IAAI,CAAC,iCAAiC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpB,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACnC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEpB,SAAS,aAAa,CAAC,OAA2B;QAChD,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACjC,IAAI,CAAC,KAAK,QAAQ;YAAE,OAAO,OAAO,CAAC;QACnC,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACnC,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAClC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,mEAAmE;QACnE,MAAM,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;QAEhF,2BAA2B;QAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC;QACnE,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAEvD,sCAAsC;QACtC,MAAM,eAAe,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC;YACnD,OAAO,GAAG,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,MAAM,uBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,wBAAwB,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAEjH,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;YAChC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QACD,IAAI,uBAAuB,EAAE,CAAC;YAC5B,SAAS,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC1C,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAEnC,MAAM,YAAY,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE1C,6BAA6B;QAC7B,MAAM,iBAAiB,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpF,MAAM,aAAa,GAAG,IAAI,iBAAiB,GAAG,CAAC;QAE/C,MAAM,sBAAsB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACvE,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACjD,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAEjD,UAAU,CAAC,IAAI,CAAC,QAAQ,WAAW,KAAK,qBAAqB,IAAI,CAAC,CAAC;QACnE,UAAU,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,MAAM,UAAU,YAAY,IAAI,CAAC,CAAC;QAEvE,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YAC7B,UAAU,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QACpD,CAAC;QAED,UAAU,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACxC,UAAU,CAAC,IAAI,CAAC,sBAAsB,WAAW,IAAI,CAAC,CAAC;QACvD,UAAU,CAAC,IAAI,CAAC,0BAA0B,aAAa,GAAG,CAAC,CAAC;QAC5D,UAAU,CAAC,IAAI,CACb,oLAAoL,CACrL,CAAC;QACF,UAAU,CAAC,IAAI,CACb,iNAAiN,CAClN,CAAC;QACF,UAAU,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;QAC1F,UAAU,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;QACzE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,UAAU,CAAC,IAAI,CAAC,gBAAgB,OAAO,CAAC,MAAM,gBAAgB,CAAC,CAAC;QAChE,UAAU,CAAC,IAAI,CAAC,kFAAkF,CAAC,CAAC;QACpG,UAAU,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;QAC/D,UAAU,CAAC,IAAI,CAAC,2BAA2B,sBAAsB,oBAAoB,CAAC,CAAC;QACvF,UAAU,CAAC,IAAI,CAAC,2FAA2F,CAAC,CAAC;QAC7G,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC;IAED,yFAAyF;IACzF,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBAClC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,iBAAiB,GAAG,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,MAAM,cAAc,GAAG,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjG,MAAM,cAAc,GAAG,GAAG,cAAc,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAE7H,OAAO;QACL,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,cAAc;QACd,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -73,6 +73,16 @@ export interface BodyEmitOptions {
|
|
|
73
73
|
forIterNext?: boolean;
|
|
74
74
|
letAssign?: boolean;
|
|
75
75
|
};
|
|
76
|
+
/** Outer-scope names the body INHERITS — typically function parameters and
|
|
77
|
+
* module-level globals the wrapper has bound. Pre-populated as the
|
|
78
|
+
* outermost `localScopes` map so an inner-block `let` that shadows ANY of
|
|
79
|
+
* these triggers the block-scope rename (closes nero red-team Challenge 2
|
|
80
|
+
* for param shadows). Each name is recorded as 'const' since the body's
|
|
81
|
+
* own re-declarations of these names go through `let` (a fresh
|
|
82
|
+
* declaration) rather than `assign`, so this annotation only governs
|
|
83
|
+
* shadow-detection — it never blocks legitimate inner reassignment of an
|
|
84
|
+
* unrelated inner binding. */
|
|
85
|
+
outerBindings?: string[];
|
|
76
86
|
}
|
|
77
87
|
/** Slice 3e — public return shape. `code` is the joined body text;
|
|
78
88
|
* `imports` is the per-handler set of import identifiers
|
|
@@ -96,6 +106,44 @@ export interface BodyEmitResult {
|
|
|
96
106
|
usedPropagation: boolean;
|
|
97
107
|
helpers: Set<string>;
|
|
98
108
|
}
|
|
109
|
+
interface BodyEmitContext {
|
|
110
|
+
gensymCounter: number;
|
|
111
|
+
imports: Set<string>;
|
|
112
|
+
/** PR-4 — runtime helper function definitions the body references.
|
|
113
|
+
* Populated lazily when codegen needs a helper (e.g. `_kern_pairs` for
|
|
114
|
+
* `each` pair-mode). Consumer emits each entry at module scope. */
|
|
115
|
+
helpers: Set<string>;
|
|
116
|
+
symbolMap: Record<string, string>;
|
|
117
|
+
shadowedSymbols: Set<string>;
|
|
118
|
+
localScopes: Array<Map<string, 'const' | 'let' | 'cell'>>;
|
|
119
|
+
regexScopes: Array<Map<string, Extract<ValueIR, {
|
|
120
|
+
kind: 'regexLit';
|
|
121
|
+
}> | null>>;
|
|
122
|
+
/** Per-scope `userName -> emittedName` map. Populated when an inner-block
|
|
123
|
+
* `let` shadows an outer binding so TS block-scope (`let x=1; if(c){let x=2}
|
|
124
|
+
* return x` → 1) survives Python's flat function-scope (would otherwise
|
|
125
|
+
* leak 2). Parallel to `localScopes`; pushed/popped together. The outermost
|
|
126
|
+
* scope never renames (function-body lets stay user-facing). Resolved via
|
|
127
|
+
* `resolveLocalRename`; consulted in ident emission. */
|
|
128
|
+
renameStack: Array<Map<string, string>>;
|
|
129
|
+
propagateStyle: 'value' | 'http-exception';
|
|
130
|
+
usedPropagation: boolean;
|
|
131
|
+
/** PR-3b differential-harness opt-in (see BodyEmitOptions.traceHooks). */
|
|
132
|
+
traceHooks?: {
|
|
133
|
+
eachIterNext?: boolean;
|
|
134
|
+
forIterNext?: boolean;
|
|
135
|
+
letAssign?: boolean;
|
|
136
|
+
};
|
|
137
|
+
/** Slice 4c review fix (OpenCode + Gemini critical) — depth of nested
|
|
138
|
+
* `try` blocks. Propagation `?` lowers to `return tmp` (or `raise
|
|
139
|
+
* HTTPException` in route mode), and BOTH bypass the enclosing
|
|
140
|
+
* `except` clause unexpectedly. Reject `?` inside try with a clear
|
|
141
|
+
* let-bind hint. Increment on try entry, decrement on try exit. */
|
|
142
|
+
tryDepth: number;
|
|
143
|
+
/** Depth of nested `finally` blocks. Propagation from finally would
|
|
144
|
+
* override pending control flow, so it gets a finally-specific error. */
|
|
145
|
+
finallyDepth: number;
|
|
146
|
+
}
|
|
99
147
|
/** PR-4 — Python helpers that normalize `each` pair-mode iteration sources.
|
|
100
148
|
* Co-located with the codegen so the production emitter and the differential
|
|
101
149
|
* harness use byte-identical definitions; consumers emit the string at module
|
|
@@ -114,7 +162,6 @@ export interface BodyEmitResult {
|
|
|
114
162
|
* async iterable; sync data is wrapped at iteration entry).
|
|
115
163
|
*
|
|
116
164
|
* Both helpers are pure functions on the input; no captures, no globals. */
|
|
117
|
-
export declare const KERN_PAIR_HELPERS_PY: string;
|
|
118
165
|
/** KERN-canonical interpolation formatter for `fmt` / template literals.
|
|
119
166
|
* Python `f"{v}"` uses `str()`, which gives `True`/`False`/`None` — diverging
|
|
120
167
|
* from KERN's canonical lowercase `true`/`false`/`null` that TS template
|
|
@@ -126,7 +173,6 @@ export declare const KERN_PAIR_HELPERS_PY: string;
|
|
|
126
173
|
* `True` → `"True"`. Co-located with the codegen so the production emitter and
|
|
127
174
|
* the differential harness use byte-identical defs; emitted at module scope
|
|
128
175
|
* via `BodyEmitResult.helpers` whenever an interpolation is wrapped. */
|
|
129
|
-
export declare const KERN_FMT_HELPER_PY: string;
|
|
130
176
|
/** Emit the body of a native KERN handler as Python source. Returns the
|
|
131
177
|
* joined body text. Each top-level line is unindented; nested `if`-bodies
|
|
132
178
|
* carry one level of 4-space indent per level of nesting.
|
|
@@ -164,3 +210,6 @@ export declare function emitNativeKernBodyPythonWithImports(handlerNode: IRNode,
|
|
|
164
210
|
* `emitPyExprCtx` which threads the live ctx (and therefore the live
|
|
165
211
|
* imports set) end-to-end. */
|
|
166
212
|
export declare function emitPyExpression(node: ValueIR, options?: BodyEmitOptions): string;
|
|
213
|
+
export declare function lowerBitwiseAndModuloAST(node: ValueIR): ValueIR;
|
|
214
|
+
export declare function registerHelpers(node: ValueIR, ctx: BodyEmitContext): void;
|
|
215
|
+
export {};
|