sveltekit-python-vercel 1.0.2 → 1.0.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/esm/src/vite/mod.js +116 -0
- package/esm/src/vite/sveltekit_python_vercel/__init__.py +0 -0
- package/esm/src/vite/sveltekit_python_vercel/bin.mjs +6 -0
- package/esm/src/vite/sveltekit_python_vercel/build.py +120 -0
- package/esm/src/vite/sveltekit_python_vercel/deploy.py +141 -0
- package/esm/src/vite/sveltekit_python_vercel/serve.py +82 -0
- package/package.json +1 -1
- package/types/src/vite/mod.d.ts +8 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { $ as run$, cd as cd$, which, path, chalk, } from "zx";
|
|
2
|
+
const get_pyServerEndpointAsString = (app_url, serve = false) => `
|
|
3
|
+
const handle = (method) => (async ({ request, fetch, url }) => {
|
|
4
|
+
const headers = new Headers()
|
|
5
|
+
headers.append('content-type', request.headers.get('content-type'));
|
|
6
|
+
headers.append('accept', request.headers.get('accept'));
|
|
7
|
+
|
|
8
|
+
let fullURL;
|
|
9
|
+
|
|
10
|
+
if (${serve}) {
|
|
11
|
+
fullURL = new URL(url.pathname, new URL('${app_url}')) + url.search;
|
|
12
|
+
} else {
|
|
13
|
+
fullURL = new URL('/api' + url.pathname, url.origin) + url.search;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log(\`PY: Reached python endpoint of \${method} \${fullURL}\`)
|
|
17
|
+
let requestBody = await request.clone().text();
|
|
18
|
+
console.log(\`PY: Body: \${requestBody}\`);
|
|
19
|
+
|
|
20
|
+
if (method === 'GET') {
|
|
21
|
+
requestBody = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return fetch(fullURL, { headers, method, body: requestBody, signal: request.signal, duplex: 'half' });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const GET = handle('GET');
|
|
28
|
+
export const POST = handle('POST');
|
|
29
|
+
export const PATCH = handle('PATCH');
|
|
30
|
+
export const PUT = handle('PUT');
|
|
31
|
+
export const DELETE = handle('DELETE');
|
|
32
|
+
`;
|
|
33
|
+
export async function sveltekit_python_vercel(opts = {}) {
|
|
34
|
+
const child_processes = [];
|
|
35
|
+
async function kill_all_process() {
|
|
36
|
+
for (const ps of child_processes) {
|
|
37
|
+
await ps.kill();
|
|
38
|
+
await ps.exitCode;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let sveltekit_url;
|
|
42
|
+
const plugin_python_serve = {
|
|
43
|
+
name: "vite-plugin-sveltekit-python-serve",
|
|
44
|
+
apply: "serve",
|
|
45
|
+
async closeBundle() {
|
|
46
|
+
await kill_all_process();
|
|
47
|
+
},
|
|
48
|
+
async configureServer({ config }) {
|
|
49
|
+
const packagelocation = path.join(config.root, "node_modules", "sveltekit-python-vercel", "esm/src/vite");
|
|
50
|
+
// copy asll +server.py files to package directory
|
|
51
|
+
run$.verbose = false;
|
|
52
|
+
run$.env.PYTHONDONTWRITEBYTECODE = "1";
|
|
53
|
+
cd$(packagelocation);
|
|
54
|
+
const python_path = opts.python_path ?? (await which("python3"));
|
|
55
|
+
const host = opts.host ?? "0.0.0.0";
|
|
56
|
+
const port = opts.port ?? 8000;
|
|
57
|
+
const local_process = run$ `${python_path} -m sveltekit_python_vercel.serve --host ${host} --port ${port} --root ${config.root}`;
|
|
58
|
+
child_processes.push(local_process);
|
|
59
|
+
sveltekit_url ??= new URL(`http://${host}:${port}`);
|
|
60
|
+
cd$(config.root);
|
|
61
|
+
// local_process.quiet(); // let it be loud for now
|
|
62
|
+
local_process.nothrow();
|
|
63
|
+
local_process.stderr.on("data", (s) => {
|
|
64
|
+
console.log(s.toString().trimEnd()); //Logs stderr always and all of stdout if 'log': True
|
|
65
|
+
});
|
|
66
|
+
local_process.stderr.on("error", (s) => {
|
|
67
|
+
console.error(chalk.red("Error: Python Serve Failed"));
|
|
68
|
+
console.error(s.toString().trimEnd());
|
|
69
|
+
});
|
|
70
|
+
local_process.stdout.on("error", (s) => {
|
|
71
|
+
console.error(chalk.red("Error: Python Serve Failed"));
|
|
72
|
+
console.error(s.toString().trimEnd());
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const plugin_python_build = {
|
|
77
|
+
name: "vite-plugin-sveltekit_python-build",
|
|
78
|
+
apply: "build",
|
|
79
|
+
async configResolved(config) {
|
|
80
|
+
console.log("PY: ROOT PATH: " + config.root);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const plugin_py_server_endpoint_serve = {
|
|
84
|
+
name: "vite-plugin-sveltekit_python-server-endpoint",
|
|
85
|
+
apply: "serve",
|
|
86
|
+
transform(src, id) {
|
|
87
|
+
// console.log("Transform function called for", id); // Add this line
|
|
88
|
+
if (/\.py$/.test(id)) {
|
|
89
|
+
if (sveltekit_url === undefined)
|
|
90
|
+
throw new Error(`${plugin_python_serve.name} failed to produce a sveltekit_url`);
|
|
91
|
+
return {
|
|
92
|
+
code: get_pyServerEndpointAsString(sveltekit_url, true),
|
|
93
|
+
map: null, // provide source map if available
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const plugin_py_server_endpoint_build = {
|
|
99
|
+
name: "vite-plugin-sveltekit_python-server-endpoint",
|
|
100
|
+
apply: "build",
|
|
101
|
+
transform(src, id) {
|
|
102
|
+
if (/\.py$/.test(id)) {
|
|
103
|
+
return {
|
|
104
|
+
code: get_pyServerEndpointAsString(new URL("http://localhost"), false),
|
|
105
|
+
map: null,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
return [
|
|
111
|
+
plugin_python_serve,
|
|
112
|
+
plugin_python_build,
|
|
113
|
+
plugin_py_server_endpoint_serve,
|
|
114
|
+
plugin_py_server_endpoint_build,
|
|
115
|
+
];
|
|
116
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env zx --install
|
|
2
|
+
|
|
3
|
+
import {$} from "zx";
|
|
4
|
+
|
|
5
|
+
const python_path = await $`which python3`;
|
|
6
|
+
await $`${python_path} ./node_modules/sveltekit-python-vercel/esm/src/vite/sveltekit_python_vercel/build.py --root . --packagedir ./node_modules/sveltekit-python-vercel/esm/src/vite/sveltekit_python_vercel`;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import glob
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path, PurePosixPath
|
|
8
|
+
|
|
9
|
+
parser = argparse.ArgumentParser(description="Run SvelteKit Python Deployment")
|
|
10
|
+
parser.add_argument("--root", default=".", help="Root directory of the SvelteKit project")
|
|
11
|
+
parser.add_argument("--packagedir", default=None, help="Directory of the sveltekit-python-vercel package")
|
|
12
|
+
args = parser.parse_args()
|
|
13
|
+
|
|
14
|
+
root_dir = Path(args.root).absolute()
|
|
15
|
+
|
|
16
|
+
func_dir = root_dir / ".vercel" / "output" / "functions" / "api" / "index.func"
|
|
17
|
+
func_dir.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
if args.packagedir:
|
|
20
|
+
shutil.copy(Path(args.packagedir).absolute() / "deploy.py", func_dir / "index.py")
|
|
21
|
+
|
|
22
|
+
# find all +server.py routes and copy them into the .func directory
|
|
23
|
+
manifest = []
|
|
24
|
+
|
|
25
|
+
for module_path in glob.glob(str(root_dir / "src/routes/**/+server.py"), recursive=True):
|
|
26
|
+
rel = Path(module_path).absolute().relative_to(root_dir / "src/routes")
|
|
27
|
+
|
|
28
|
+
# replace square brackets with curly brackets
|
|
29
|
+
rel = Path(str(rel).replace("[", "{").replace("]", "}"))
|
|
30
|
+
|
|
31
|
+
# remove any SvteleKit groups from the URL
|
|
32
|
+
parts = [
|
|
33
|
+
p for p in PurePosixPath(rel).parts
|
|
34
|
+
if not (p.startswith("(") and p.endswith(")"))
|
|
35
|
+
]
|
|
36
|
+
rel = Path(*parts)
|
|
37
|
+
|
|
38
|
+
target_dir = func_dir / rel.parent
|
|
39
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
|
|
41
|
+
shutil.copy(module_path, target_dir / rel.name)
|
|
42
|
+
|
|
43
|
+
if not (target_dir / "__init__.py").exists():
|
|
44
|
+
(target_dir / "__init__.py").touch()
|
|
45
|
+
|
|
46
|
+
# build the API route
|
|
47
|
+
parent = PurePosixPath(rel).parent
|
|
48
|
+
if str(parent) == ".":
|
|
49
|
+
api_route = "/api"
|
|
50
|
+
else:
|
|
51
|
+
api_route = "/api/" + str(parent)
|
|
52
|
+
|
|
53
|
+
manifest.append({"file": str(PurePosixPath(rel)), "route": api_route})
|
|
54
|
+
print(f"PYTHON ENDPOINT: {module_path} → {api_route}")
|
|
55
|
+
|
|
56
|
+
(func_dir / "_manifest.json").write_text(json.dumps(manifest, indent=2))
|
|
57
|
+
|
|
58
|
+
# bundle the dependency file here
|
|
59
|
+
dep_files = ["requirements.txt", "pyproject.toml", "uv.lock", "Pipfile", "Pipfile.lock"]
|
|
60
|
+
found_dep = False
|
|
61
|
+
for dep_file in dep_files:
|
|
62
|
+
src = root_dir / dep_file
|
|
63
|
+
if src.exists():
|
|
64
|
+
shutil.copy(src, func_dir / dep_file)
|
|
65
|
+
print(f"PYTHON ENDPOINT: Bundled {dep_file}")
|
|
66
|
+
found_dep = True
|
|
67
|
+
|
|
68
|
+
if not found_dep:
|
|
69
|
+
(func_dir / "requirements.txt").write_text("fastapi\nuvicorn\n")
|
|
70
|
+
print("PYTHON ENDPOINT: No dependency file found, created minimal requirements.txt")
|
|
71
|
+
|
|
72
|
+
# pre-install Python packages into _deps/ so they are available in the lambda env
|
|
73
|
+
dep_dir = func_dir / "_deps"
|
|
74
|
+
dep_dir.mkdir(exist_ok=True)
|
|
75
|
+
pip_cmd = [sys.executable, "-m", "pip", "install", "--target", str(dep_dir), "--quiet"]
|
|
76
|
+
if (func_dir / "requirements.txt").exists():
|
|
77
|
+
pip_cmd += ["-r", str(func_dir / "requirements.txt")]
|
|
78
|
+
subprocess.run(pip_cmd, check=True)
|
|
79
|
+
print("PYTHON ENDPOINT: Installed deps from requirements.txt")
|
|
80
|
+
elif (func_dir / "pyproject.toml").exists():
|
|
81
|
+
# extract dependencies from pyproject.toml
|
|
82
|
+
try:
|
|
83
|
+
import tomllib
|
|
84
|
+
except ImportError:
|
|
85
|
+
import tomli as tomllib # type: ignore
|
|
86
|
+
with open(func_dir / "pyproject.toml", "rb") as _f:
|
|
87
|
+
_pyproject = tomllib.load(_f)
|
|
88
|
+
_deps_list = _pyproject.get("project", {}).get("dependencies", [])
|
|
89
|
+
if _deps_list:
|
|
90
|
+
subprocess.run(pip_cmd + _deps_list, check=True)
|
|
91
|
+
print(f"PYTHON ENDPOINT: Installed {len(_deps_list)} deps from pyproject.toml")
|
|
92
|
+
|
|
93
|
+
# python3.12 is the AWS Lambda runtime string
|
|
94
|
+
vc_config = {"runtime": "python3.12", "handler": "index.handler"}
|
|
95
|
+
(func_dir / ".vc-config.json").write_text(json.dumps(vc_config, indent=2))
|
|
96
|
+
print("PYTHON ENDPOINT: Created .vc-config.json")
|
|
97
|
+
|
|
98
|
+
# patch the SvelteKit config.json that the endpoints are rerouted to python
|
|
99
|
+
config_path = root_dir / ".vercel" / "output" / "config.json"
|
|
100
|
+
if config_path.exists():
|
|
101
|
+
config = json.loads(config_path.read_text())
|
|
102
|
+
routes = config.get("routes", [])
|
|
103
|
+
|
|
104
|
+
python_route = {"src": "^/api(/.*)?$", "dest": "api/index"}
|
|
105
|
+
|
|
106
|
+
# routes /api/* to python before SvelteKit catches it
|
|
107
|
+
fs_idx = next(
|
|
108
|
+
(i for i, r in enumerate(routes) if r.get("handle") == "filesystem"),
|
|
109
|
+
0,
|
|
110
|
+
)
|
|
111
|
+
routes.insert(fs_idx, python_route)
|
|
112
|
+
config["routes"] = routes
|
|
113
|
+
|
|
114
|
+
config_path.write_text(json.dumps(config, indent=2))
|
|
115
|
+
print("PYTHON ENDPOINT: Patched .vercel/output/config.json")
|
|
116
|
+
else:
|
|
117
|
+
print(
|
|
118
|
+
"WARNING: .vercel/output/config.json not found. "
|
|
119
|
+
"Make sure to run `vite build` before this script."
|
|
120
|
+
)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
import importlib.util
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from urllib.parse import urlsplit
|
|
8
|
+
|
|
9
|
+
_base = Path(__file__).parent
|
|
10
|
+
_deps = _base / "_deps"
|
|
11
|
+
if _deps.exists() and str(_deps) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_deps))
|
|
13
|
+
if str(_base) not in sys.path:
|
|
14
|
+
sys.path.insert(0, str(_base))
|
|
15
|
+
|
|
16
|
+
from fastapi import FastAPI, Request
|
|
17
|
+
from fastapi.responses import JSONResponse
|
|
18
|
+
|
|
19
|
+
app = FastAPI()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load_module(file_path: Path):
|
|
23
|
+
spec = importlib.util.spec_from_file_location("_route_module", file_path)
|
|
24
|
+
mod = importlib.util.module_from_spec(spec)
|
|
25
|
+
spec.loader.exec_module(mod)
|
|
26
|
+
return mod
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_manifest_path = _base / "_manifest.json"
|
|
30
|
+
if _manifest_path.exists():
|
|
31
|
+
for _entry in json.loads(_manifest_path.read_text()):
|
|
32
|
+
_mod = _load_module(_base / _entry["file"])
|
|
33
|
+
_route = _entry["route"]
|
|
34
|
+
|
|
35
|
+
for _method in ["GET", "POST", "PATCH", "PUT", "DELETE"]:
|
|
36
|
+
_has_upper = hasattr(_mod, _method)
|
|
37
|
+
_has_lower = hasattr(_mod, _method.lower())
|
|
38
|
+
|
|
39
|
+
if _has_upper and _has_lower:
|
|
40
|
+
raise Exception(
|
|
41
|
+
f"Duplicate method {_method} and {_method.lower()} in {_route}"
|
|
42
|
+
)
|
|
43
|
+
elif _has_upper:
|
|
44
|
+
app.add_api_route(_route, getattr(_mod, _method), methods=[_method])
|
|
45
|
+
print(f"PYTHON ENDPOINT: Registered {_method} {_route}")
|
|
46
|
+
elif _has_lower:
|
|
47
|
+
app.add_api_route(_route, getattr(_mod, _method.lower()), methods=[_method])
|
|
48
|
+
print(f"PYTHON ENDPOINT: Registered {_method} {_route}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.exception_handler(Exception)
|
|
52
|
+
async def _exception_handler(request: Request, exc: Exception):
|
|
53
|
+
return JSONResponse(status_code=500, content={"error": str(exc)})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def _dispatch(scope: dict, body: bytes) -> tuple[int, dict, bytes]:
|
|
57
|
+
"""Run FastAPI for one HTTP request and collect the response."""
|
|
58
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
59
|
+
queue.put_nowait({"type": "http.request", "body": body, "more_body": False})
|
|
60
|
+
|
|
61
|
+
status = 500
|
|
62
|
+
resp_headers: dict = {}
|
|
63
|
+
resp_body = b""
|
|
64
|
+
|
|
65
|
+
async def receive():
|
|
66
|
+
return await queue.get()
|
|
67
|
+
|
|
68
|
+
async def send(message):
|
|
69
|
+
nonlocal status, resp_body
|
|
70
|
+
if message["type"] == "http.response.start":
|
|
71
|
+
status = message["status"]
|
|
72
|
+
for k, v in message.get("headers", []):
|
|
73
|
+
if isinstance(k, bytes):
|
|
74
|
+
k = k.decode()
|
|
75
|
+
if isinstance(v, bytes):
|
|
76
|
+
v = v.decode()
|
|
77
|
+
k = k.lower()
|
|
78
|
+
if k in resp_headers:
|
|
79
|
+
existing = resp_headers[k]
|
|
80
|
+
resp_headers[k] = (existing if isinstance(existing, list) else [existing]) + [v]
|
|
81
|
+
else:
|
|
82
|
+
resp_headers[k] = v
|
|
83
|
+
elif message["type"] == "http.response.body":
|
|
84
|
+
resp_body += message.get("body", b"")
|
|
85
|
+
|
|
86
|
+
await app(scope, receive, send)
|
|
87
|
+
return status, resp_headers, resp_body
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def handler(event, context):
|
|
91
|
+
payload = json.loads(event.get("body") or "{}")
|
|
92
|
+
|
|
93
|
+
# parse the path and query string
|
|
94
|
+
parsed = urlsplit(payload.get("path", "/"))
|
|
95
|
+
path = parsed.path or "/"
|
|
96
|
+
query = (parsed.query or "").encode()
|
|
97
|
+
|
|
98
|
+
# Build ASGI-style header list
|
|
99
|
+
raw_headers: dict = payload.get("headers") or {}
|
|
100
|
+
headers_list = []
|
|
101
|
+
host = payload.get("host", "localhost")
|
|
102
|
+
scheme = "https"
|
|
103
|
+
for k, v in raw_headers.items():
|
|
104
|
+
k_lower = k.lower()
|
|
105
|
+
if k_lower == "host":
|
|
106
|
+
host = v
|
|
107
|
+
if k_lower == "x-forwarded-proto":
|
|
108
|
+
scheme = v
|
|
109
|
+
headers_list.append((k_lower.encode(), str(v).encode()))
|
|
110
|
+
|
|
111
|
+
scope = {
|
|
112
|
+
"type": "http",
|
|
113
|
+
"http_version": "1.1",
|
|
114
|
+
"method": payload.get("method", "GET").upper(),
|
|
115
|
+
"path": path,
|
|
116
|
+
"raw_path": path.encode(),
|
|
117
|
+
"query_string": query,
|
|
118
|
+
"root_path": "",
|
|
119
|
+
"headers": headers_list,
|
|
120
|
+
"server": (host, 443 if scheme == "https" else 80),
|
|
121
|
+
"client": (raw_headers.get("x-real-ip", "127.0.0.1"), 0),
|
|
122
|
+
"scheme": scheme,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
body = payload.get("body") or b""
|
|
126
|
+
if payload.get("encoding") == "base64":
|
|
127
|
+
body = base64.b64decode(body)
|
|
128
|
+
elif isinstance(body, str):
|
|
129
|
+
body = body.encode()
|
|
130
|
+
|
|
131
|
+
status, resp_headers, resp_body = asyncio.run(_dispatch(scope, body))
|
|
132
|
+
|
|
133
|
+
result: dict = {"statusCode": status, "headers": resp_headers}
|
|
134
|
+
if resp_body:
|
|
135
|
+
try:
|
|
136
|
+
result["body"] = resp_body.decode("utf-8")
|
|
137
|
+
except UnicodeDecodeError:
|
|
138
|
+
result["body"] = base64.b64encode(resp_body).decode()
|
|
139
|
+
result["encoding"] = "base64"
|
|
140
|
+
|
|
141
|
+
return result
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import glob
|
|
3
|
+
import importlib
|
|
4
|
+
import importlib.util
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path, PurePosixPath
|
|
7
|
+
|
|
8
|
+
import uvicorn
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
|
|
11
|
+
parser = argparse.ArgumentParser(description="Run Sveltekit Python Server")
|
|
12
|
+
parser.add_argument("--host", default="0.0.0.0", help="Server hostname")
|
|
13
|
+
parser.add_argument("--port", type=int, default=8000, help="Server port")
|
|
14
|
+
parser.add_argument("--root", default=".", help="Directory where the API is located")
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
app = FastAPI()
|
|
18
|
+
|
|
19
|
+
root_dir = Path(args.root).absolute()
|
|
20
|
+
|
|
21
|
+
api_dir = Path("./sveltekit_python_vercel").absolute()
|
|
22
|
+
|
|
23
|
+
route_dir = root_dir.joinpath("src/routes")
|
|
24
|
+
|
|
25
|
+
watch_modules = [] # list of modules to watch for changes
|
|
26
|
+
|
|
27
|
+
for module_path in glob.glob(
|
|
28
|
+
route_dir.joinpath("**/+server.py").as_posix(), recursive=True
|
|
29
|
+
):
|
|
30
|
+
abs_module_path = Path(module_path).absolute()
|
|
31
|
+
|
|
32
|
+
watch_modules.append(abs_module_path.parent.as_posix())
|
|
33
|
+
|
|
34
|
+
api_route = api_dir.joinpath(abs_module_path.relative_to(root_dir / "src/routes"))
|
|
35
|
+
|
|
36
|
+
if not api_route.parent.exists():
|
|
37
|
+
api_route.parent.mkdir(parents=True)
|
|
38
|
+
|
|
39
|
+
# copy module path to api_route
|
|
40
|
+
shutil.copy(module_path, api_route.parent)
|
|
41
|
+
|
|
42
|
+
module_name = api_route.stem
|
|
43
|
+
|
|
44
|
+
spec = importlib.util.spec_from_file_location(module_name, api_route)
|
|
45
|
+
mod = importlib.util.module_from_spec(spec)
|
|
46
|
+
spec.loader.exec_module(mod)
|
|
47
|
+
|
|
48
|
+
# Get the relative path of the module from the API directory
|
|
49
|
+
rel_path = api_route.relative_to(api_dir)
|
|
50
|
+
|
|
51
|
+
# Convert the relative path to a string and remove the file extension
|
|
52
|
+
api_path = f"/{rel_path.parent}"
|
|
53
|
+
|
|
54
|
+
# Replace square brackets with curly brackets
|
|
55
|
+
api_path = api_path.replace("[", "{").replace("]", "}")
|
|
56
|
+
|
|
57
|
+
# remove any groups from the URL
|
|
58
|
+
api_path = str(PurePosixPath(*[part for part in PurePosixPath(api_path).parts if not part.startswith("(") and not part.endswith(")")]))
|
|
59
|
+
|
|
60
|
+
# Add endpoints
|
|
61
|
+
for method in ["GET", "POST", "PATCH", "PUT", "DELETE"]:
|
|
62
|
+
# Check for duplicate methods
|
|
63
|
+
if hasattr(mod, method) and hasattr(mod, method.lower()):
|
|
64
|
+
raise Exception(
|
|
65
|
+
f"Duplicate method {method} and {method.lower()} in {api_route}"
|
|
66
|
+
)
|
|
67
|
+
elif hasattr(mod, method):
|
|
68
|
+
app.add_api_route(api_path, getattr(mod, method), methods=[method])
|
|
69
|
+
elif hasattr(mod, method.lower()):
|
|
70
|
+
app.add_api_route(api_path, getattr(mod, method.lower()), methods=[method])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
uvicorn.run(
|
|
75
|
+
"sveltekit_python_vercel.serve:app",
|
|
76
|
+
host=args.host,
|
|
77
|
+
port=args.port,
|
|
78
|
+
log_level="info",
|
|
79
|
+
reload=True,
|
|
80
|
+
reload_includes=[*set(watch_modules)],
|
|
81
|
+
reload_excludes=[api_dir.as_posix()],
|
|
82
|
+
)
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"module": "./esm/mod.js",
|
|
3
3
|
"types": "./types/mod.d.ts",
|
|
4
4
|
"name": "sveltekit-python-vercel",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.3",
|
|
6
6
|
"description": "Write Sveltekit server endpoints in Python and seamlessly deploy to Vercel",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|