Revert "[python] support Sanic >=21 and python >= 3.10" (#8241)

- Reverts vercel/vercel#8045
- Fixes vercel/vercel#8231
This commit is contained in:
Steven
2022-07-25 13:40:21 -04:00
committed by GitHub
parent 1ee9a96a62
commit 5af65d5a24
6 changed files with 7 additions and 432 deletions

View File

@@ -167,23 +167,13 @@ elif 'app' in __vc_variables:
else:
print('using Asynchronous Server Gateway Interface (ASGI)')
# Originally authored by Jordan Eremieff and included under MIT license:
# https://github.com/erm/mangum/blob/07ce20a0e2f67c5c2593258a92c03fdc66d9edda/mangum/__init__.py
# https://github.com/erm/mangum/blob/07ce20a0e2f67c5c2593258a92c03fdc66d9edda/LICENSE
# https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/mangum/__init__.py
# https://github.com/erm/mangum/blob/b4d21c8f5e304a3e17b88bc9fa345106acc50ad7/LICENSE
import asyncio
import enum
import logging
from contextlib import ExitStack
from urllib.parse import urlparse
from werkzeug.datastructures import Headers
def get_event_loop():
try:
return asyncio.get_running_loop()
except:
if sys.version_info < (3, 10):
return asyncio.get_event_loop()
else:
return asyncio.get_event_loop_policy().get_event_loop()
class ASGICycleState(enum.Enum):
REQUEST = enum.auto()
@@ -204,8 +194,8 @@ elif 'app' in __vc_variables:
ASGI instance using the connection scope.
Runs until the response is completely read from the application.
"""
loop = get_event_loop()
self.app_queue = asyncio.Queue()
loop = asyncio.new_event_loop()
self.app_queue = asyncio.Queue(loop=loop)
self.put_message({'type': 'http.request', 'body': body, 'more_body': False})
asgi_instance = app(self.scope, self.receive, self.send)
@@ -267,156 +257,6 @@ elif 'app' in __vc_variables:
self.response['body'] = base64.b64encode(self.body).decode('utf-8')
self.response['encoding'] = 'base64'
class LifespanFailure(Exception):
"""Raise when a lifespan failure event is sent by an application."""
class LifespanUnsupported(Exception):
"""Raise when lifespan events are not supported by an application."""
class UnexpectedMessage(Exception):
"""Raise when an unexpected message type is received during an ASGI cycle."""
class LifespanCycleState(enum.Enum):
"""
The state of the ASGI `lifespan` connection.
* **CONNECTING** - Initial state. The ASGI application instance will be run with
the connection scope containing the `lifespan` type.
* **STARTUP** - The lifespan startup event has been pushed to the queue to be
received by the application.
* **SHUTDOWN** - The lifespan shutdown event has been pushed to the queue to be
received by the application.
* **FAILED** - A lifespan failure has been detected, and the connection will be
closed with an error.
* **UNSUPPORTED** - An application attempted to send a message before receiving
the lifepan startup event. If the lifespan argument is "on", then the connection
will be closed with an error.
"""
CONNECTING = enum.auto()
STARTUP = enum.auto()
SHUTDOWN = enum.auto()
FAILED = enum.auto()
UNSUPPORTED = enum.auto()
class Lifespan:
def __init__(self, app):
self.app = app
self.state = LifespanCycleState.CONNECTING
self.exception = None
self.logger = logging.getLogger('lifespan')
self.loop = get_event_loop()
self.app_queue = asyncio.Queue()
self.startup_event = asyncio.Event()
self.shutdown_event = asyncio.Event()
def __enter__(self) -> None:
"""Runs the event loop for application startup."""
self.loop.create_task(self.run())
self.loop.run_until_complete(self.startup())
def __exit__(
self,
exc_type,
exc_value,
traceback,
) -> None:
"""Runs the event loop for application shutdown."""
self.loop.run_until_complete(self.shutdown())
async def run(self):
"""Calls the application with the `lifespan` connection scope."""
try:
await self.app(
{"type": "lifespan", "asgi": {"spec_version": "2.0", "version": "3.0"}},
self.receive,
self.send,
)
except LifespanUnsupported:
self.logger.info("ASGI 'lifespan' protocol appears unsupported.")
except (LifespanFailure, UnexpectedMessage) as exc:
self.exception = exc
except BaseException as exc:
self.logger.error("Exception in 'lifespan' protocol.", exc_info=exc)
finally:
self.startup_event.set()
self.shutdown_event.set()
async def send(self, message):
"""Awaited by the application to send ASGI `lifespan` events."""
message_type = message["type"]
if self.state is LifespanCycleState.CONNECTING:
# If a message is sent before the startup event is received by the
# application, then assume that lifespan is unsupported.
self.state = LifespanCycleState.UNSUPPORTED
raise LifespanUnsupported("Lifespan protocol appears unsupported.")
if message_type not in (
"lifespan.startup.complete",
"lifespan.shutdown.complete",
"lifespan.startup.failed",
"lifespan.shutdown.failed",
):
self.state = LifespanCycleState.FAILED
raise UnexpectedMessage(f"Unexpected '{message_type}' event received.")
if self.state is LifespanCycleState.STARTUP:
if message_type == "lifespan.startup.complete":
self.startup_event.set()
elif message_type == "lifespan.startup.failed":
self.state = LifespanCycleState.FAILED
self.startup_event.set()
message_value = message.get("message", "")
raise LifespanFailure(f"Lifespan startup failure. {message_value}")
elif self.state is LifespanCycleState.SHUTDOWN:
if message_type == "lifespan.shutdown.complete":
self.shutdown_event.set()
elif message_type == "lifespan.shutdown.failed":
self.state = LifespanCycleState.FAILED
self.shutdown_event.set()
message_value = message.get("message", "")
raise LifespanFailure(f"Lifespan shutdown failure. {message_value}")
async def receive(self):
"""Awaited by the application to receive ASGI `lifespan` events."""
if self.state is LifespanCycleState.CONNECTING:
# Connection established. The next event returned by the queue will be
# `lifespan.startup` to inform the application that the connection is
# ready to receive lfiespan messages.
self.state = LifespanCycleState.STARTUP
elif self.state is LifespanCycleState.STARTUP:
# Connection shutting down. The next event returned by the queue will be
# `lifespan.shutdown` to inform the application that the connection is now
# closing so that it may perform cleanup.
self.state = LifespanCycleState.SHUTDOWN
return await self.app_queue.get()
async def startup(self) -> None:
"""Pushes the `lifespan` startup event to the queue and handles errors."""
await self.app_queue.put({"type": "lifespan.startup"})
await self.startup_event.wait()
if self.state is LifespanCycleState.FAILED:
raise LifespanFailure(self.exception)
if not self.exception:
self.logger.info("Application startup complete.")
else:
self.logger.info("Application startup failed.")
async def shutdown(self) -> None:
"""Pushes the `lifespan` shutdown event to the queue and handles errors."""
await self.app_queue.put({"type": "lifespan.shutdown"})
await self.shutdown_event.wait()
if self.state is LifespanCycleState.FAILED:
raise LifespanFailure(self.exception)
def vc_handler(event, context):
payload = json.loads(event['body'])
@@ -449,13 +289,9 @@ elif 'app' in __vc_variables:
'raw_path': path.encode(),
}
with ExitStack() as stack:
lifespan = Lifespan(__vc_module.app)
stack.enter_context(lifespan)
asgi_cycle = ASGICycle(scope)
response = asgi_cycle(__vc_module.app, body)
return response
asgi_cycle = ASGICycle(scope)
response = asgi_cycle(__vc_module.app, body)
return response
else:
print('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')