@vercel/python 3.0.5 → 3.1.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/dist/index.js +2 -3
- package/package.json +3 -3
- package/vc_init.py +171 -7
package/dist/index.js
CHANGED
|
@@ -3819,7 +3819,6 @@ const allOptions = [
|
|
|
3819
3819
|
discontinueDate: new Date('2022-07-18'),
|
|
3820
3820
|
},
|
|
3821
3821
|
];
|
|
3822
|
-
const upstreamProvider = 'This change is the result of a decision made by an upstream infrastructure provider (AWS)';
|
|
3823
3822
|
function getDevPythonVersion() {
|
|
3824
3823
|
// Use the system-installed version of `python3` when running `vercel dev`
|
|
3825
3824
|
return {
|
|
@@ -3854,12 +3853,12 @@ function getSupportedPythonVersion({ isDev, pipLockPythonVersion, }) {
|
|
|
3854
3853
|
throw new build_utils_1.NowBuildError({
|
|
3855
3854
|
code: 'BUILD_UTILS_PYTHON_VERSION_DISCONTINUED',
|
|
3856
3855
|
link: 'http://vercel.link/python-version',
|
|
3857
|
-
message: `Python version "${selection.version}" detected in Pipfile.lock is discontinued and must be upgraded
|
|
3856
|
+
message: `Python version "${selection.version}" detected in Pipfile.lock is discontinued and must be upgraded.`,
|
|
3858
3857
|
});
|
|
3859
3858
|
}
|
|
3860
3859
|
if (selection.discontinueDate) {
|
|
3861
3860
|
const d = selection.discontinueDate.toISOString().split('T')[0];
|
|
3862
|
-
console.warn(`Error: Python version "${selection.version}" detected in Pipfile.lock
|
|
3861
|
+
console.warn(`Error: Python version "${selection.version}" detected in Pipfile.lock has reached End-of-Life. Deployments created on or after ${d} will fail to build. http://vercel.link/python-version`);
|
|
3863
3862
|
}
|
|
3864
3863
|
return selection;
|
|
3865
3864
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/python",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/execa": "^0.9.0",
|
|
25
25
|
"@types/jest": "27.4.1",
|
|
26
|
-
"@vercel/build-utils": "5.0.
|
|
26
|
+
"@vercel/build-utils": "5.0.4",
|
|
27
27
|
"@vercel/ncc": "0.24.0",
|
|
28
28
|
"execa": "^1.0.0",
|
|
29
29
|
"typescript": "4.3.4"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "3d3774ee7e3d344b3292d2166d485bdf41a68d4c"
|
|
32
32
|
}
|
package/vc_init.py
CHANGED
|
@@ -167,13 +167,23 @@ elif 'app' in __vc_variables:
|
|
|
167
167
|
else:
|
|
168
168
|
print('using Asynchronous Server Gateway Interface (ASGI)')
|
|
169
169
|
# Originally authored by Jordan Eremieff and included under MIT license:
|
|
170
|
-
# https://github.com/erm/mangum/blob/
|
|
171
|
-
# https://github.com/erm/mangum/blob/
|
|
170
|
+
# https://github.com/erm/mangum/blob/07ce20a0e2f67c5c2593258a92c03fdc66d9edda/mangum/__init__.py
|
|
171
|
+
# https://github.com/erm/mangum/blob/07ce20a0e2f67c5c2593258a92c03fdc66d9edda/LICENSE
|
|
172
172
|
import asyncio
|
|
173
173
|
import enum
|
|
174
|
+
import logging
|
|
175
|
+
from contextlib import ExitStack
|
|
174
176
|
from urllib.parse import urlparse
|
|
175
177
|
from werkzeug.datastructures import Headers
|
|
176
178
|
|
|
179
|
+
def get_event_loop():
|
|
180
|
+
try:
|
|
181
|
+
return asyncio.get_running_loop()
|
|
182
|
+
except:
|
|
183
|
+
if sys.version_info < (3, 10):
|
|
184
|
+
return asyncio.get_event_loop()
|
|
185
|
+
else:
|
|
186
|
+
return asyncio.get_event_loop_policy().get_event_loop()
|
|
177
187
|
|
|
178
188
|
class ASGICycleState(enum.Enum):
|
|
179
189
|
REQUEST = enum.auto()
|
|
@@ -194,8 +204,8 @@ elif 'app' in __vc_variables:
|
|
|
194
204
|
ASGI instance using the connection scope.
|
|
195
205
|
Runs until the response is completely read from the application.
|
|
196
206
|
"""
|
|
197
|
-
loop =
|
|
198
|
-
self.app_queue = asyncio.Queue(
|
|
207
|
+
loop = get_event_loop()
|
|
208
|
+
self.app_queue = asyncio.Queue()
|
|
199
209
|
self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
|
|
200
210
|
|
|
201
211
|
asgi_instance = app(self.scope, self.receive, self.send)
|
|
@@ -257,6 +267,156 @@ elif 'app' in __vc_variables:
|
|
|
257
267
|
self.response['body'] = base64.b64encode(self.body).decode('utf-8')
|
|
258
268
|
self.response['encoding'] = 'base64'
|
|
259
269
|
|
|
270
|
+
class LifespanFailure(Exception):
|
|
271
|
+
"""Raise when a lifespan failure event is sent by an application."""
|
|
272
|
+
|
|
273
|
+
class LifespanUnsupported(Exception):
|
|
274
|
+
"""Raise when lifespan events are not supported by an application."""
|
|
275
|
+
|
|
276
|
+
class UnexpectedMessage(Exception):
|
|
277
|
+
"""Raise when an unexpected message type is received during an ASGI cycle."""
|
|
278
|
+
|
|
279
|
+
class LifespanCycleState(enum.Enum):
|
|
280
|
+
"""
|
|
281
|
+
The state of the ASGI `lifespan` connection.
|
|
282
|
+
* **CONNECTING** - Initial state. The ASGI application instance will be run with
|
|
283
|
+
the connection scope containing the `lifespan` type.
|
|
284
|
+
* **STARTUP** - The lifespan startup event has been pushed to the queue to be
|
|
285
|
+
received by the application.
|
|
286
|
+
* **SHUTDOWN** - The lifespan shutdown event has been pushed to the queue to be
|
|
287
|
+
received by the application.
|
|
288
|
+
* **FAILED** - A lifespan failure has been detected, and the connection will be
|
|
289
|
+
closed with an error.
|
|
290
|
+
* **UNSUPPORTED** - An application attempted to send a message before receiving
|
|
291
|
+
the lifepan startup event. If the lifespan argument is "on", then the connection
|
|
292
|
+
will be closed with an error.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
CONNECTING = enum.auto()
|
|
296
|
+
STARTUP = enum.auto()
|
|
297
|
+
SHUTDOWN = enum.auto()
|
|
298
|
+
FAILED = enum.auto()
|
|
299
|
+
UNSUPPORTED = enum.auto()
|
|
300
|
+
|
|
301
|
+
class Lifespan:
|
|
302
|
+
|
|
303
|
+
def __init__(self, app):
|
|
304
|
+
self.app = app
|
|
305
|
+
self.state = LifespanCycleState.CONNECTING
|
|
306
|
+
self.exception = None
|
|
307
|
+
self.logger = logging.getLogger('lifespan')
|
|
308
|
+
self.loop = get_event_loop()
|
|
309
|
+
self.app_queue = asyncio.Queue()
|
|
310
|
+
self.startup_event = asyncio.Event()
|
|
311
|
+
self.shutdown_event = asyncio.Event()
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def __enter__(self) -> None:
|
|
315
|
+
"""Runs the event loop for application startup."""
|
|
316
|
+
self.loop.create_task(self.run())
|
|
317
|
+
self.loop.run_until_complete(self.startup())
|
|
318
|
+
|
|
319
|
+
def __exit__(
|
|
320
|
+
self,
|
|
321
|
+
exc_type,
|
|
322
|
+
exc_value,
|
|
323
|
+
traceback,
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Runs the event loop for application shutdown."""
|
|
326
|
+
self.loop.run_until_complete(self.shutdown())
|
|
327
|
+
|
|
328
|
+
async def run(self):
|
|
329
|
+
"""Calls the application with the `lifespan` connection scope."""
|
|
330
|
+
try:
|
|
331
|
+
await self.app(
|
|
332
|
+
{"type": "lifespan", "asgi": {"spec_version": "2.0", "version": "3.0"}},
|
|
333
|
+
self.receive,
|
|
334
|
+
self.send,
|
|
335
|
+
)
|
|
336
|
+
except LifespanUnsupported:
|
|
337
|
+
self.logger.info("ASGI 'lifespan' protocol appears unsupported.")
|
|
338
|
+
except (LifespanFailure, UnexpectedMessage) as exc:
|
|
339
|
+
self.exception = exc
|
|
340
|
+
except BaseException as exc:
|
|
341
|
+
self.logger.error("Exception in 'lifespan' protocol.", exc_info=exc)
|
|
342
|
+
finally:
|
|
343
|
+
self.startup_event.set()
|
|
344
|
+
self.shutdown_event.set()
|
|
345
|
+
|
|
346
|
+
async def send(self, message):
|
|
347
|
+
"""Awaited by the application to send ASGI `lifespan` events."""
|
|
348
|
+
message_type = message["type"]
|
|
349
|
+
|
|
350
|
+
if self.state is LifespanCycleState.CONNECTING:
|
|
351
|
+
# If a message is sent before the startup event is received by the
|
|
352
|
+
# application, then assume that lifespan is unsupported.
|
|
353
|
+
self.state = LifespanCycleState.UNSUPPORTED
|
|
354
|
+
raise LifespanUnsupported("Lifespan protocol appears unsupported.")
|
|
355
|
+
|
|
356
|
+
if message_type not in (
|
|
357
|
+
"lifespan.startup.complete",
|
|
358
|
+
"lifespan.shutdown.complete",
|
|
359
|
+
"lifespan.startup.failed",
|
|
360
|
+
"lifespan.shutdown.failed",
|
|
361
|
+
):
|
|
362
|
+
self.state = LifespanCycleState.FAILED
|
|
363
|
+
raise UnexpectedMessage(f"Unexpected '{message_type}' event received.")
|
|
364
|
+
|
|
365
|
+
if self.state is LifespanCycleState.STARTUP:
|
|
366
|
+
if message_type == "lifespan.startup.complete":
|
|
367
|
+
self.startup_event.set()
|
|
368
|
+
elif message_type == "lifespan.startup.failed":
|
|
369
|
+
self.state = LifespanCycleState.FAILED
|
|
370
|
+
self.startup_event.set()
|
|
371
|
+
message_value = message.get("message", "")
|
|
372
|
+
raise LifespanFailure(f"Lifespan startup failure. {message_value}")
|
|
373
|
+
|
|
374
|
+
elif self.state is LifespanCycleState.SHUTDOWN:
|
|
375
|
+
if message_type == "lifespan.shutdown.complete":
|
|
376
|
+
self.shutdown_event.set()
|
|
377
|
+
elif message_type == "lifespan.shutdown.failed":
|
|
378
|
+
self.state = LifespanCycleState.FAILED
|
|
379
|
+
self.shutdown_event.set()
|
|
380
|
+
message_value = message.get("message", "")
|
|
381
|
+
raise LifespanFailure(f"Lifespan shutdown failure. {message_value}")
|
|
382
|
+
|
|
383
|
+
async def receive(self):
|
|
384
|
+
"""Awaited by the application to receive ASGI `lifespan` events."""
|
|
385
|
+
if self.state is LifespanCycleState.CONNECTING:
|
|
386
|
+
|
|
387
|
+
# Connection established. The next event returned by the queue will be
|
|
388
|
+
# `lifespan.startup` to inform the application that the connection is
|
|
389
|
+
# ready to receive lfiespan messages.
|
|
390
|
+
self.state = LifespanCycleState.STARTUP
|
|
391
|
+
|
|
392
|
+
elif self.state is LifespanCycleState.STARTUP:
|
|
393
|
+
|
|
394
|
+
# Connection shutting down. The next event returned by the queue will be
|
|
395
|
+
# `lifespan.shutdown` to inform the application that the connection is now
|
|
396
|
+
# closing so that it may perform cleanup.
|
|
397
|
+
self.state = LifespanCycleState.SHUTDOWN
|
|
398
|
+
|
|
399
|
+
return await self.app_queue.get()
|
|
400
|
+
|
|
401
|
+
async def startup(self) -> None:
|
|
402
|
+
"""Pushes the `lifespan` startup event to the queue and handles errors."""
|
|
403
|
+
await self.app_queue.put({"type": "lifespan.startup"})
|
|
404
|
+
await self.startup_event.wait()
|
|
405
|
+
if self.state is LifespanCycleState.FAILED:
|
|
406
|
+
raise LifespanFailure(self.exception)
|
|
407
|
+
|
|
408
|
+
if not self.exception:
|
|
409
|
+
self.logger.info("Application startup complete.")
|
|
410
|
+
else:
|
|
411
|
+
self.logger.info("Application startup failed.")
|
|
412
|
+
|
|
413
|
+
async def shutdown(self) -> None:
|
|
414
|
+
"""Pushes the `lifespan` shutdown event to the queue and handles errors."""
|
|
415
|
+
await self.app_queue.put({"type": "lifespan.shutdown"})
|
|
416
|
+
await self.shutdown_event.wait()
|
|
417
|
+
if self.state is LifespanCycleState.FAILED:
|
|
418
|
+
raise LifespanFailure(self.exception)
|
|
419
|
+
|
|
260
420
|
def vc_handler(event, context):
|
|
261
421
|
payload = json.loads(event['body'])
|
|
262
422
|
|
|
@@ -289,9 +449,13 @@ elif 'app' in __vc_variables:
|
|
|
289
449
|
'raw_path': path.encode(),
|
|
290
450
|
}
|
|
291
451
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
452
|
+
with ExitStack() as stack:
|
|
453
|
+
lifespan = Lifespan(__vc_module.app)
|
|
454
|
+
stack.enter_context(lifespan)
|
|
455
|
+
|
|
456
|
+
asgi_cycle = ASGICycle(scope)
|
|
457
|
+
response = asgi_cycle(__vc_module.app, body)
|
|
458
|
+
return response
|
|
295
459
|
|
|
296
460
|
else:
|
|
297
461
|
print('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
|