@vercel/python 6.0.2 → 6.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/dist/index.js +22 -2
- package/package.json +1 -1
- package/vc_init.py +144 -77
- package/vc_init_dev_asgi.py +3 -2
package/dist/index.js
CHANGED
|
@@ -3004,7 +3004,12 @@ async function exportRequirementsFromUv(projectDir, uvPath, options = {}) {
|
|
|
3004
3004
|
if (!uvPath) {
|
|
3005
3005
|
throw new Error("uv is not available to export requirements");
|
|
3006
3006
|
}
|
|
3007
|
-
const args = [
|
|
3007
|
+
const args = [
|
|
3008
|
+
"export",
|
|
3009
|
+
"--no-default-groups",
|
|
3010
|
+
"--no-emit-workspace",
|
|
3011
|
+
"--no-editable"
|
|
3012
|
+
];
|
|
3008
3013
|
if (locked) {
|
|
3009
3014
|
args.push("--frozen");
|
|
3010
3015
|
}
|
|
@@ -3915,6 +3920,18 @@ var build = async ({
|
|
|
3915
3920
|
targetDir: vendorBaseDir,
|
|
3916
3921
|
meta
|
|
3917
3922
|
});
|
|
3923
|
+
if (framework !== "flask") {
|
|
3924
|
+
await installRequirement({
|
|
3925
|
+
pythonPath: pythonVersion.pythonPath,
|
|
3926
|
+
pipPath: pythonVersion.pipPath,
|
|
3927
|
+
uvPath,
|
|
3928
|
+
dependency: "uvicorn",
|
|
3929
|
+
version: "0.38.0",
|
|
3930
|
+
workPath,
|
|
3931
|
+
targetDir: vendorBaseDir,
|
|
3932
|
+
meta
|
|
3933
|
+
});
|
|
3934
|
+
}
|
|
3918
3935
|
let installedFromProjectFiles = false;
|
|
3919
3936
|
if (uvLockDir) {
|
|
3920
3937
|
(0, import_build_utils6.debug)('Found "uv.lock"');
|
|
@@ -4026,7 +4043,10 @@ var build = async ({
|
|
|
4026
4043
|
"**/__pycache__/**",
|
|
4027
4044
|
"**/.mypy_cache/**",
|
|
4028
4045
|
"**/.ruff_cache/**",
|
|
4029
|
-
"**/public/**"
|
|
4046
|
+
"**/public/**",
|
|
4047
|
+
"**/pnpm-lock.yaml",
|
|
4048
|
+
"**/yarn.lock",
|
|
4049
|
+
"**/package-lock.json"
|
|
4030
4050
|
];
|
|
4031
4051
|
const lambdaEnv = {};
|
|
4032
4052
|
lambdaEnv.PYTHONPATH = vendorDir;
|
package/package.json
CHANGED
package/vc_init.py
CHANGED
|
@@ -210,12 +210,17 @@ if 'VERCEL_IPC_PATH' in os.environ:
|
|
|
210
210
|
|
|
211
211
|
|
|
212
212
|
# Import relative path https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
try:
|
|
214
|
+
user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute
|
|
215
|
+
__vc_spec = util.spec_from_file_location("__VC_HANDLER_MODULE_NAME", user_mod_path)
|
|
216
|
+
__vc_module = util.module_from_spec(__vc_spec)
|
|
217
|
+
sys.modules["__VC_HANDLER_MODULE_NAME"] = __vc_module
|
|
218
|
+
__vc_spec.loader.exec_module(__vc_module)
|
|
219
|
+
__vc_variables = dir(__vc_module)
|
|
220
|
+
except Exception:
|
|
221
|
+
_stderr(f'Error importing __VC_HANDLER_ENTRYPOINT:')
|
|
222
|
+
_stderr(traceback.format_exc())
|
|
223
|
+
exit(1)
|
|
219
224
|
|
|
220
225
|
_use_legacy_asyncio = sys.version_info < (3, 10)
|
|
221
226
|
|
|
@@ -231,6 +236,95 @@ def format_headers(headers, decode=False):
|
|
|
231
236
|
return keyToList
|
|
232
237
|
|
|
233
238
|
|
|
239
|
+
class ASGIMiddleware:
|
|
240
|
+
"""
|
|
241
|
+
ASGI middleware that preserves Vercel IPC semantics for request lifecycle:
|
|
242
|
+
- Handles /_vercel/ping
|
|
243
|
+
- Extracts x-vercel-internal-* headers and removes them from downstream app
|
|
244
|
+
- Sets request context into `storage` for logging/metrics
|
|
245
|
+
- Emits handler-started and end IPC messages
|
|
246
|
+
"""
|
|
247
|
+
def __init__(self, app):
|
|
248
|
+
self.app = app
|
|
249
|
+
|
|
250
|
+
async def __call__(self, scope, receive, send):
|
|
251
|
+
if scope.get('type') != 'http':
|
|
252
|
+
# Non-HTTP traffic is forwarded verbatim
|
|
253
|
+
await self.app(scope, receive, send)
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
if scope.get('path') == '/_vercel/ping':
|
|
257
|
+
await send({
|
|
258
|
+
'type': 'http.response.start',
|
|
259
|
+
'status': 200,
|
|
260
|
+
'headers': [],
|
|
261
|
+
})
|
|
262
|
+
await send({
|
|
263
|
+
'type': 'http.response.body',
|
|
264
|
+
'body': b'',
|
|
265
|
+
'more_body': False,
|
|
266
|
+
})
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
# Extract internal headers and set per-request context
|
|
270
|
+
headers_list = scope.get('headers', []) or []
|
|
271
|
+
new_headers = []
|
|
272
|
+
invocation_id = "0"
|
|
273
|
+
request_id = 0
|
|
274
|
+
|
|
275
|
+
def _b2s(b: bytes) -> str:
|
|
276
|
+
try:
|
|
277
|
+
return b.decode()
|
|
278
|
+
except Exception:
|
|
279
|
+
return ''
|
|
280
|
+
|
|
281
|
+
for k, v in headers_list:
|
|
282
|
+
key = _b2s(k).lower()
|
|
283
|
+
val = _b2s(v)
|
|
284
|
+
if key == 'x-vercel-internal-invocation-id':
|
|
285
|
+
invocation_id = val
|
|
286
|
+
continue
|
|
287
|
+
if key == 'x-vercel-internal-request-id':
|
|
288
|
+
request_id = int(val) if val.isdigit() else 0
|
|
289
|
+
continue
|
|
290
|
+
if key in ('x-vercel-internal-span-id', 'x-vercel-internal-trace-id'):
|
|
291
|
+
continue
|
|
292
|
+
new_headers.append((k, v))
|
|
293
|
+
|
|
294
|
+
new_scope = dict(scope)
|
|
295
|
+
new_scope['headers'] = new_headers
|
|
296
|
+
|
|
297
|
+
# Announce handler start and set context for logging/metrics
|
|
298
|
+
send_message({
|
|
299
|
+
"type": "handler-started",
|
|
300
|
+
"payload": {
|
|
301
|
+
"handlerStartedAt": int(time.time() * 1000),
|
|
302
|
+
"context": {
|
|
303
|
+
"invocationId": invocation_id,
|
|
304
|
+
"requestId": request_id,
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
token = storage.set({
|
|
310
|
+
"invocationId": invocation_id,
|
|
311
|
+
"requestId": request_id,
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
await self.app(new_scope, receive, send)
|
|
316
|
+
finally:
|
|
317
|
+
storage.reset(token)
|
|
318
|
+
send_message({
|
|
319
|
+
"type": "end",
|
|
320
|
+
"payload": {
|
|
321
|
+
"context": {
|
|
322
|
+
"invocationId": invocation_id,
|
|
323
|
+
"requestId": request_id,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
|
|
234
328
|
if 'VERCEL_IPC_PATH' in os.environ:
|
|
235
329
|
start_time = time.time()
|
|
236
330
|
|
|
@@ -424,80 +518,53 @@ if 'VERCEL_IPC_PATH' in os.environ:
|
|
|
424
518
|
if hasattr(response, 'close'):
|
|
425
519
|
response.close()
|
|
426
520
|
else:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
'method': self.command,
|
|
456
|
-
'path': url.path,
|
|
457
|
-
'raw_path': url.path.encode(),
|
|
458
|
-
}
|
|
521
|
+
# ASGI: Run with Uvicorn so we get proper lifespan and protocol handling
|
|
522
|
+
try:
|
|
523
|
+
import uvicorn
|
|
524
|
+
except Exception:
|
|
525
|
+
_stderr('Uvicorn is required to run ASGI apps. Please ensure it is installed.')
|
|
526
|
+
exit(1)
|
|
527
|
+
|
|
528
|
+
# Prefer a callable app.asgi when available; some frameworks expose a boolean here
|
|
529
|
+
user_app_candidate = getattr(__vc_module.app, 'asgi', None)
|
|
530
|
+
user_app = user_app_candidate if callable(user_app_candidate) else __vc_module.app
|
|
531
|
+
asgi_app = ASGIMiddleware(user_app)
|
|
532
|
+
|
|
533
|
+
# Pre-bind a socket to obtain an ephemeral port for IPC announcement
|
|
534
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
535
|
+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
536
|
+
sock.bind(('127.0.0.1', 0))
|
|
537
|
+
sock.listen(2048)
|
|
538
|
+
http_port = sock.getsockname()[1]
|
|
539
|
+
|
|
540
|
+
config = uvicorn.Config(
|
|
541
|
+
app=asgi_app,
|
|
542
|
+
fd=sock.fileno(),
|
|
543
|
+
lifespan='auto',
|
|
544
|
+
access_log=False,
|
|
545
|
+
log_config=None,
|
|
546
|
+
log_level='warning',
|
|
547
|
+
)
|
|
548
|
+
server = uvicorn.Server(config)
|
|
459
549
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
550
|
+
send_message({
|
|
551
|
+
"type": "server-started",
|
|
552
|
+
"payload": {
|
|
553
|
+
"initDuration": int((time.time() - start_time) * 1000),
|
|
554
|
+
"httpPort": http_port,
|
|
555
|
+
}
|
|
556
|
+
})
|
|
465
557
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
app_queue.put_nowait({'type': 'http.request', 'body': body, 'more_body': False})
|
|
472
|
-
|
|
473
|
-
# Prepare ASGI receive function
|
|
474
|
-
async def receive():
|
|
475
|
-
message = await app_queue.get()
|
|
476
|
-
return message
|
|
477
|
-
|
|
478
|
-
# Prepare ASGI send function
|
|
479
|
-
response_started = False
|
|
480
|
-
async def send(event):
|
|
481
|
-
nonlocal response_started
|
|
482
|
-
if event['type'] == 'http.response.start':
|
|
483
|
-
self.send_response(event['status'])
|
|
484
|
-
if 'headers' in event:
|
|
485
|
-
for name, value in event['headers']:
|
|
486
|
-
self.send_header(name.decode(), value.decode())
|
|
487
|
-
self.end_headers()
|
|
488
|
-
response_started = True
|
|
489
|
-
elif event['type'] == 'http.response.body':
|
|
490
|
-
self.wfile.write(event['body'])
|
|
491
|
-
if not event.get('more_body', False):
|
|
492
|
-
self.wfile.flush()
|
|
558
|
+
# Mark IPC as ready and flush any buffered init logs
|
|
559
|
+
_ipc_ready = True
|
|
560
|
+
for m in _init_log_buf:
|
|
561
|
+
send_message(m)
|
|
562
|
+
_init_log_buf.clear()
|
|
493
563
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
loop.run_until_complete(asgi_task)
|
|
499
|
-
else:
|
|
500
|
-
asyncio.run(asgi_instance)
|
|
564
|
+
# Run the server (blocking)
|
|
565
|
+
server.run()
|
|
566
|
+
# If the server ever returns, exit
|
|
567
|
+
sys.exit(0)
|
|
501
568
|
|
|
502
569
|
if 'Handler' in locals():
|
|
503
570
|
server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
|
package/vc_init_dev_asgi.py
CHANGED
|
@@ -39,8 +39,9 @@ if _app is None:
|
|
|
39
39
|
f"Missing 'app' in module '{USER_MODULE}'. Define `app = ...` (ASGI app)."
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
|
|
42
|
+
# Prefer a callable app.asgi when available; some frameworks expose a boolean here
|
|
43
|
+
_CAND = getattr(_app, 'asgi', None)
|
|
44
|
+
USER_ASGI_APP = _CAND if callable(_CAND) else _app
|
|
44
45
|
|
|
45
46
|
PUBLIC_DIR = 'public'
|
|
46
47
|
|