brief-extractor/backend/venv/lib/python3.10/site-packages/quart/ctx.py
2026-03-06 18:42:46 +00:00

483 lines
15 KiB
Python
Executable file

from __future__ import annotations
import sys
from contextvars import Token
from functools import wraps
from types import TracebackType
from typing import Any
from typing import Callable
from typing import cast
from typing import TYPE_CHECKING
from flask.ctx import _AppCtxGlobals as _AppCtxGlobals # noqa: F401
from werkzeug.exceptions import HTTPException
from .globals import _cv_app
from .globals import _cv_request
from .globals import _cv_websocket
from .sessions import SessionMixin # noqa
from .signals import appcontext_popped
from .signals import appcontext_pushed
from .typing import AfterRequestCallable
from .typing import AfterWebsocketCallable
from .wrappers import BaseRequestWebsocket
from .wrappers import Request
from .wrappers import Websocket
if TYPE_CHECKING:
from .app import Quart # noqa
_sentinel = object()
class _BaseRequestWebsocketContext:
"""A base context relating to either request or websockets, bound to the
current task.
Attributes:
app: The app itself.
request_websocket: The request or websocket itself.
url_adapter: An adapter bound to this request.
session: The session information relating to this request.
"""
def __init__(
self,
app: Quart,
request_websocket: BaseRequestWebsocket,
session: SessionMixin | None = None,
) -> None:
self.app = app
self.request_websocket = request_websocket
self.url_adapter = app.create_url_adapter(self.request_websocket)
self.request_websocket.routing_exception = None
self.request_websocket.json_module = app.json
self.session = session
self.preserved = False
self._cv_tokens: list[tuple[Token, AppContext | None]] = []
def copy(self) -> _BaseRequestWebsocketContext:
return self.__class__(self.app, self.request_websocket, self.session)
def match_request(self) -> None:
"""Match the request against the adapter.
Override this method to configure request matching, it should
set the request url_rule and view_args and optionally a
routing_exception.
"""
try:
(
self.request_websocket.url_rule,
self.request_websocket.view_args,
) = self.url_adapter.match( # type: ignore
return_rule=True
) # noqa
except HTTPException as exception:
self.request_websocket.routing_exception = exception
async def push(self) -> None:
raise NotImplementedError()
async def pop(self, exc: BaseException | None) -> None:
raise NotImplementedError()
async def auto_pop(self, exc: BaseException | None) -> None:
if self.request_websocket.scope.get("_quart._preserve_context", False) or (
exc is not None and self.app.config["PRESERVE_CONTEXT_ON_EXCEPTION"]
):
self.preserved = True
else:
await self.pop(exc)
async def __aenter__(self) -> _BaseRequestWebsocketContext:
await self.push()
return self
async def __aexit__(
self, exc_type: type, exc_value: BaseException, tb: TracebackType
) -> None:
await self.auto_pop(exc_value)
async def _push_appctx(self, token: Token) -> None:
app_ctx = _cv_app.get(None)
if app_ctx is None or app_ctx.app is not self.app:
app_ctx = self.app.app_context()
await app_ctx.push()
else:
app_ctx = None
self._cv_tokens.append((token, app_ctx))
async def _push(self) -> None:
if self.session is None:
session_interface = self.app.session_interface
self.session = await self.app.ensure_async(session_interface.open_session)(
self.app, self.request_websocket
)
if self.session is None:
self.session = await session_interface.make_null_session(self.app)
if self.url_adapter is not None:
self.match_request()
class RequestContext(_BaseRequestWebsocketContext):
"""The context relating to the specific request, bound to the current task.
Do not use directly, prefer the
:func:`~quart.Quart.request_context` or
:func:`~quart.Quart.test_request_context` instead.
Attributes:
_after_request_functions: List of functions to execute after the current
request, see :func:`after_this_request`.
"""
def __init__(
self,
app: Quart,
request: Request,
session: SessionMixin | None = None,
) -> None:
super().__init__(app, request, session)
self.flashes = None
self._after_request_functions: list[AfterRequestCallable] = []
@property
def request(self) -> Request:
return cast(Request, self.request_websocket)
async def push(self) -> None:
await super()._push_appctx(_cv_request.set(self))
await super()._push()
async def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
try:
if len(self._cv_tokens) == 1:
if exc is _sentinel:
exc = sys.exc_info()[1]
await self.app.do_teardown_request(exc, self)
request_close = getattr(self.request_websocket, "close", None)
if request_close is not None:
await request_close()
finally:
ctx = _cv_request.get()
token, app_ctx = self._cv_tokens.pop()
_cv_request.reset(token)
if app_ctx is not None:
await app_ctx.pop(exc)
if ctx is not self:
raise AssertionError(
f"Popped wrong request context. ({ctx!r} instead of {self!r})"
)
async def __aenter__(self) -> RequestContext:
await self.push()
return self
class WebsocketContext(_BaseRequestWebsocketContext):
"""The context relating to the specific websocket, bound to the current task.
Do not use directly, prefer the
:func:`~quart.Quart.websocket_context` or
:func:`~quart.Quart.test_websocket_context` instead.
Attributes:
_after_websocket_functions: List of functions to execute after the current
websocket, see :func:`after_this_websocket`.
"""
def __init__(
self,
app: Quart,
request: Websocket,
session: SessionMixin | None = None,
) -> None:
super().__init__(app, request, session)
self._after_websocket_functions: list[AfterWebsocketCallable] = []
@property
def websocket(self) -> Websocket:
return cast(Websocket, self.request_websocket)
async def push(self) -> None:
await super()._push_appctx(_cv_websocket.set(self))
await super()._push()
async def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
try:
if len(self._cv_tokens) == 1:
if exc is _sentinel:
exc = sys.exc_info()[1]
await self.app.do_teardown_websocket(exc, self)
finally:
ctx = _cv_websocket.get()
token, app_ctx = self._cv_tokens.pop()
_cv_websocket.reset(token)
if app_ctx is not None:
await app_ctx.pop(exc)
if ctx is not self:
raise AssertionError(
f"Popped wrong request context. ({ctx!r} instead of {self!r})"
)
async def __aenter__(self) -> WebsocketContext:
await self.push()
return self
class AppContext:
"""The context relating to the app bound to the current task.
Do not use directly, prefer the
:func:`~quart.Quart.app_context` instead.
Attributes:
app: The app itself.
url_adapter: An adapter bound to the server, but not a
specific task, useful for route building.
g: An instance of the ctx globals class.
"""
def __init__(self, app: Quart) -> None:
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
self._cv_tokens: list[Token] = []
def copy(self) -> AppContext:
app_context = self.__class__(self.app)
app_context.g = self.g
return app_context
async def push(self) -> None:
self._cv_tokens.append(_cv_app.set(self))
await appcontext_pushed.send_async(
self.app,
_sync_wrapper=self.app.ensure_async, # type: ignore[arg-type]
)
async def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
try:
if len(self._cv_tokens) == 1:
if exc is _sentinel:
exc = sys.exc_info()[1]
await self.app.do_teardown_appcontext(exc)
finally:
ctx = _cv_app.get()
_cv_app.reset(self._cv_tokens.pop())
if ctx is not self:
raise AssertionError(
f"Popped wrong app context. ({ctx!r} instead of {self!r})"
)
await appcontext_popped.send_async(
self.app,
_sync_wrapper=self.app.ensure_async, # type: ignore[arg-type]
)
async def __aenter__(self) -> AppContext:
await self.push()
return self
async def __aexit__(
self, exc_type: type, exc_value: BaseException, tb: TracebackType
) -> None:
await self.pop(exc_value)
def after_this_request(func: AfterRequestCallable) -> AfterRequestCallable:
"""Schedule the func to be called after the current request.
This is useful in situations whereby you want an after request
function for a specific route or circumstance only, for example,
.. code-block:: python
def index():
@after_this_request
def set_cookie(response):
response.set_cookie('special', 'value')
return response
...
"""
ctx = _cv_request.get(None)
if ctx is None:
raise RuntimeError("Not within a request context")
ctx._after_request_functions.append(func)
return func
def after_this_websocket(func: AfterWebsocketCallable) -> AfterWebsocketCallable:
"""Schedule the func to be called after the current websocket.
This is useful in situations whereby you want an after websocket
function for a specific route or circumstance only, for example,
.. note::
The response is an optional argument, and will only be
passed if the websocket was not active (i.e. there was an
error).
.. code-block:: python
def index():
@after_this_websocket
def set_cookie(response: Optional[Response]):
response.set_cookie('special', 'value')
return response
...
"""
ctx = _cv_websocket.get(None)
if ctx is None:
raise RuntimeError("Not within a websocket context")
ctx._after_websocket_functions.append(func)
return func
def copy_current_app_context(func: Callable) -> Callable:
"""Share the current app context with the function decorated.
The app context is local per task and hence will not be available
in any other task. This decorator can be used to make the context
available,
.. code-block:: python
@copy_current_app_context
async def within_context() -> None:
name = current_app.name
...
"""
if not has_app_context():
raise RuntimeError("Attempt to copy app context outside of a app context")
app_context = _cv_app.get().copy()
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
async with app_context:
return await app_context.app.ensure_async(func)(*args, **kwargs)
return wrapper
def copy_current_request_context(func: Callable) -> Callable:
"""Share the current request context with the function decorated.
The request context is local per task and hence will not be
available in any other task. This decorator can be used to make
the context available,
.. code-block:: python
@copy_current_request_context
async def within_context() -> None:
method = request.method
...
"""
if not has_request_context():
raise RuntimeError(
"Attempt to copy request context outside of a request context"
)
request_context = _cv_request.get().copy()
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
async with request_context:
return await request_context.app.ensure_async(func)(*args, **kwargs)
return wrapper
def copy_current_websocket_context(func: Callable) -> Callable:
"""Share the current websocket context with the function decorated.
The websocket context is local per task and hence will not be
available in any other task. This decorator can be used to make
the context available,
.. code-block:: python
@copy_current_websocket_context
async def within_context() -> None:
method = websocket.method
...
"""
if not has_websocket_context():
raise RuntimeError(
"Attempt to copy websocket context outside of a websocket context"
)
websocket_context = _cv_websocket.get().copy()
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
async with websocket_context:
return await websocket_context.app.ensure_async(func)(*args, **kwargs)
return wrapper
def has_app_context() -> bool:
"""Check if execution is within an app context.
This allows a controlled way to act if there is an app context
available, or silently not act if not. For example,
.. code-block:: python
if has_app_context():
log.info("Executing in %s context", current_app.name)
See also :func:`has_request_context`
"""
return _cv_app.get(None) is not None
def has_request_context() -> bool:
"""Check if execution is within a request context.
This allows a controlled way to act if there is a request context
available, or silently not act if not. For example,
.. code-block:: python
if has_request_context():
log.info("Request endpoint %s", request.endpoint)
See also :func:`has_app_context`.
"""
return _cv_request.get(None) is not None
def has_websocket_context() -> bool:
"""Check if execution is within a websocket context.
This allows a controlled way to act if there is a websocket
context available, or silently not act if not. For example,
.. code-block:: python
if has_websocket_context():
log.info("Websocket endpoint %s", websocket.endpoint)
See also :func:`has_app_context`.
"""
return _cv_websocket.get(None) is not None