Skip to content

Commit

Permalink
App context wrappers
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed May 22, 2024
1 parent c444883 commit 5eb9f44
Showing 1 changed file with 57 additions and 12 deletions.
69 changes: 57 additions & 12 deletions src/coaster/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
TYPE_CHECKING,
Any,
AnyStr,
Generic,
NoReturn,
Optional,
TypeVar,
Expand All @@ -36,6 +37,7 @@
abort as flask_abort,
current_app as flask_current_app,
g as flask_g,
has_app_context as flask_has_app_context,
has_request_context as flask_has_request_context,
make_response as flask_make_response,
redirect as flask_redirect,
Expand All @@ -60,6 +62,7 @@
abort as quart_abort,
current_app as quart_current_app,
g as quart_g,
has_app_context as quart_has_app_context,
has_request_context as quart_has_request_context,
make_response as quart_make_response,
redirect as quart_redirect,
Expand All @@ -75,6 +78,7 @@
quart_app_ctx = None # type: ignore[assignment]
quart_current_app = None # type: ignore[assignment]
quart_g = None # type: ignore[assignment]
quart_has_app_context = None # type: ignore[assignment]
quart_has_request_context = None # type: ignore[assignment]
quart_redirect = None # type: ignore[assignment]
quart_render_template = None # type: ignore[assignment]
Expand Down Expand Up @@ -109,6 +113,7 @@
'SansIoRequest',
'SansIoResponse',
'abort',
'app_ctx_object',
'app_ctx',
'async_make_response',
'async_render_template_string',
Expand All @@ -118,6 +123,7 @@
'current_app',
'ensure_sync',
'g',
'has_app_context',
'has_request_context',
'json_dump',
'json_dumps',
Expand Down Expand Up @@ -154,7 +160,11 @@ def default(o: Any) -> Any:
return DefaultJSONProvider.default(o)


class QuartFlaskWrapper:
_QS = TypeVar('_QS', bound=Any)
_FS = TypeVar('_FS', bound=Any)


class QuartFlaskWrapper(Generic[_QS, _FS]):
"""
Proxy to Quart or Flask source objects.
Expand All @@ -163,10 +173,12 @@ class QuartFlaskWrapper:
"""

__name__: str
_quart_source: Any
_flask_source: Any
_quart_source: _QS
_flask_source: _FS

def __init__(self, name: str, quart_source: Any, flask_source: Any) -> None:
def __init__(
self, name: str, quart_source: Optional[_QS], flask_source: Optional[_FS]
) -> None:
object.__setattr__(self, '__name__', name)
object.__setattr__(self, '_quart_source', quart_source)
object.__setattr__(self, '_flask_source', flask_source)
Expand Down Expand Up @@ -196,8 +208,8 @@ def __delattr__(self, name: str) -> None:
delattr(self._flask_source, name)


class QuartFlaskCollectionWrapper(QuartFlaskWrapper):
"""Proxy to Quart or Flask source object with iterable API."""
class QuartFlaskCollectionWrapper(QuartFlaskWrapper[_QS, _FS]):
"""Proxy to Quart or Flask source object with Iterable API."""

def __contains__(self, item: str) -> bool:
if qs := self._quart_source:
Expand All @@ -218,8 +230,8 @@ def __len__(self) -> int:
Collection.register(QuartFlaskCollectionWrapper)


class QuartFlaskDictWrapper(QuartFlaskCollectionWrapper):
"""Proxy to Quart or Flask source objects with a dict API."""
class QuartFlaskDictWrapper(QuartFlaskCollectionWrapper[_QS, _FS]):
"""Proxy to Quart or Flask source objects with a MutableMapping API."""

def __getitem__(self, key: Any) -> Any:
if qs := self._quart_source:
Expand Down Expand Up @@ -280,11 +292,44 @@ def __ne__(self, other: object) -> bool:
def current_app_object() -> Optional[Union[Flask, Quart]]:
"""Get current app from Quart or Flask (unwrapping the proxy)."""
# pylint: disable=protected-access
if quart_current_app:
return quart_current_app._get_current_object() # type: ignore[attr-defined]
if flask_current_app:

# This implementation avoids calling ``bool(quart_current_app)`` as that will
# effectively retrieve the context var twice. We optimize for faster turnaround
# when an app context is present
if quart_current_app is not None:
try:
return quart_current_app._get_current_object() # type: ignore[attr-defined]
except RuntimeError:
pass
try:
return flask_current_app._get_current_object() # type: ignore[attr-defined]
return None
except RuntimeError:
return None


def app_ctx_object() -> Optional[Union[FlaskAppContext, QuartAppContext]]:
"""Get the current app context object from Quart or Flask (unwrapping the proxy)."""
# pylint: disable=protected-access

# This implementation avoids calling ``bool(quart_app_ctx)`` as that will
# effectively retrieve the context var twice. We optimize for faster turnaround
# when an app context is present
if quart_app_ctx is not None:
try:
return quart_app_ctx._get_current_object() # type: ignore[attr-defined]
except RuntimeError:
pass
try:
return flask_app_ctx._get_current_object() # type: ignore[attr-defined]
except RuntimeError:
return None


def has_app_context() -> bool:
"""Check for app context in Quart or Flask."""
return (
quart_has_app_context is not None and quart_has_app_context()
) or flask_has_app_context()


def has_request_context() -> bool:
Expand Down

0 comments on commit 5eb9f44

Please sign in to comment.