259 lines
8.1 KiB
Python
259 lines
8.1 KiB
Python
import os
|
|
import pkgutil
|
|
import sys
|
|
from functools import wraps
|
|
from pathlib import Path
|
|
from typing import Any, Callable, List, Optional, Tuple, Union
|
|
from urllib.parse import quote
|
|
|
|
from .ctx import _app_ctx_stack, _request_ctx_stack
|
|
from .globals import current_app, request, session
|
|
from .routing import BuildError
|
|
from .signals import message_flashed
|
|
from .wrappers import Response
|
|
|
|
locked_cached_property = property
|
|
|
|
|
|
def get_debug_flag() -> bool:
|
|
""" Reads QUART_DEBUG environment variable to determine whether to run
|
|
the app in debug mode. If unset, and development mode has been
|
|
configured, it will be enabled automatically.
|
|
"""
|
|
value = os.getenv('QUART_DEBUG', None)
|
|
|
|
if value is None:
|
|
return 'development' == get_env()
|
|
|
|
return value.lower() not in {'0', 'false', 'no'}
|
|
|
|
|
|
def get_env(default: Optional[str]= 'production') -> str:
|
|
"""Reads QUART_ENV environment variable to determine in which environment
|
|
the app is running on. Defaults to 'production' when unset.
|
|
"""
|
|
return os.getenv('QUART_ENV', default)
|
|
|
|
|
|
async def make_response(*args: Any) -> Response:
|
|
"""Create a response, a simple wrapper function.
|
|
|
|
This is most useful when you want to alter a Response before
|
|
returning it, for example
|
|
|
|
.. code-block:: python
|
|
|
|
response = make_response(render_template('index.html'))
|
|
response.headers['X-Header'] = 'Something'
|
|
|
|
"""
|
|
if not args:
|
|
return current_app.response_class()
|
|
if len(args) == 1:
|
|
args = args[0]
|
|
|
|
return await current_app.make_response(args)
|
|
|
|
|
|
async def make_push_promise(path: str) -> None:
|
|
"""Create a push promise, a simple wrapper function.
|
|
|
|
This takes a path that should be pushed to the client if the
|
|
protocol is HTTP/2.
|
|
|
|
"""
|
|
return await request.send_push_promise(path)
|
|
|
|
|
|
async def flash(message: str, category: str='message') -> None:
|
|
"""Add a message (with optional category) to the session store.
|
|
|
|
This is typically used to flash a message to a user that will be
|
|
stored in the session and shown during some other request. For
|
|
example,
|
|
|
|
.. code-block:: python
|
|
|
|
@app.route('/login', methods=['POST'])
|
|
async def login():
|
|
...
|
|
await flash('Login successful')
|
|
return redirect(url_for('index'))
|
|
|
|
allows the index route to show the flashed messsages, without
|
|
having to accept the message as an argument or otherwise. See
|
|
:func:`~quart.helpers.get_flashed_messages` for message retrieval.
|
|
"""
|
|
flashes = session.get('_flashes', [])
|
|
flashes.append((category, message))
|
|
session['_flashes'] = flashes
|
|
await message_flashed.send(
|
|
current_app._get_current_object(), message=message, category=category,
|
|
)
|
|
|
|
|
|
def get_flashed_messages(
|
|
with_categories: bool=False,
|
|
category_filter: List[str]=[],
|
|
) -> Union[List[str], List[Tuple[str, str]]]:
|
|
"""Retrieve the flashed messages stored in the session.
|
|
|
|
This is mostly useful in templates where it is exposed as a global
|
|
function, for example
|
|
|
|
.. code-block:: html+jinja
|
|
|
|
<ul>
|
|
{% for message in get_flashed_messages() %}
|
|
<li>{{ message }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
|
|
Note that caution is required for usage of ``category_filter`` as
|
|
all messages will be popped, but only those matching the filter
|
|
returned. See :func:`~quart.helpers.flash` for message creation.
|
|
"""
|
|
flashes = session.pop('_flashes') if '_flashes' in session else []
|
|
if category_filter:
|
|
flashes = [flash for flash in flashes if flash[0] in category_filter]
|
|
if not with_categories:
|
|
flashes = [flash[1] for flash in flashes]
|
|
return flashes
|
|
|
|
|
|
def get_template_attribute(template_name: str, attribute: str) -> Any:
|
|
"""Load a attribute from a template.
|
|
|
|
This is useful in Python code in order to use attributes in
|
|
templates.
|
|
|
|
Arguments:
|
|
template_name: To load the attribute from.
|
|
attribute: The attribute name to load
|
|
"""
|
|
return getattr(current_app.jinja_env.get_template(template_name).module, attribute)
|
|
|
|
|
|
def url_for(
|
|
endpoint: str,
|
|
*,
|
|
_anchor: Optional[str]=None,
|
|
_external: Optional[bool]=None,
|
|
_method: Optional[str]=None,
|
|
_scheme: Optional[str]=None,
|
|
**values: Any,
|
|
) -> str:
|
|
"""Return the url for a specific endpoint.
|
|
|
|
This is most useful in templates and redirects to create a URL
|
|
that can be used in the browser.
|
|
|
|
Arguments:
|
|
endpoint: The endpoint to build a url for, if prefixed with
|
|
``.`` it targets endpoint's in the current blueprint.
|
|
_anchor: Additional anchor text to append (i.e. #text).
|
|
_external: Return an absolute url for external (to app) usage.
|
|
_method: The method to consider alongside the endpoint.
|
|
_scheme: A specific scheme to use.
|
|
values: The values to build into the URL, as specified in
|
|
the endpoint rule.
|
|
"""
|
|
app_context = _app_ctx_stack.top
|
|
request_context = _request_ctx_stack.top
|
|
|
|
if request_context is not None:
|
|
url_adapter = request_context.url_adapter
|
|
if endpoint.startswith('.'):
|
|
if request.blueprint is not None:
|
|
endpoint = request.blueprint + endpoint
|
|
else:
|
|
endpoint = endpoint[1:]
|
|
if _external is None:
|
|
_external = False
|
|
elif app_context is not None:
|
|
url_adapter = app_context.url_adapter
|
|
if _external is None:
|
|
_external = True
|
|
else:
|
|
raise RuntimeError('Cannot create a url outside of an application context')
|
|
|
|
if url_adapter is None:
|
|
raise RuntimeError(
|
|
'Unable to create a url adapter, try setting the the SERVER_NAME config variable.'
|
|
)
|
|
if _scheme is not None and not _external:
|
|
raise ValueError('External must be True for scheme usage')
|
|
|
|
app_context.app.inject_url_defaults(endpoint, values)
|
|
try:
|
|
url = url_adapter.build(
|
|
endpoint, values, method=_method, scheme=_scheme, external=_external,
|
|
)
|
|
except BuildError as error:
|
|
return app_context.app.handle_url_build_error(error, endpoint, values)
|
|
|
|
if _anchor is not None:
|
|
quoted_anchor = quote(_anchor)
|
|
url = f"{url}#{quoted_anchor}"
|
|
return url
|
|
|
|
|
|
def stream_with_context(func: Callable) -> Callable:
|
|
"""Share the current request context with a generator.
|
|
|
|
This allows the request context to be accessed within a streaming
|
|
generator, for example,
|
|
|
|
.. code-block:: python
|
|
|
|
@app.route('/')
|
|
def index() -> AsyncGenerator[bytes, None]:
|
|
@stream_with_context
|
|
async def generator() -> bytes:
|
|
yield request.method.encode()
|
|
yield b' '
|
|
yield request.path.encode()
|
|
|
|
return generator()
|
|
|
|
"""
|
|
request_context = _request_ctx_stack.top.copy()
|
|
|
|
@wraps(func)
|
|
async def generator(*args: Any, **kwargs: Any) -> Any:
|
|
async with request_context:
|
|
async for data in func(*args, **kwargs):
|
|
yield data
|
|
return generator
|
|
|
|
|
|
def _endpoint_from_view_func(view_func: Callable) -> str:
|
|
return view_func.__name__
|
|
|
|
|
|
def find_package(name: str) -> Tuple[Optional[Path], Path]:
|
|
"""Finds packages install prefix (or None) and it's containing Folder
|
|
"""
|
|
module = name.split(".")[0]
|
|
loader = pkgutil.get_loader(module)
|
|
if name == "__main__" or loader is None:
|
|
package_path = Path.cwd()
|
|
else:
|
|
if hasattr(loader, 'get_filename'):
|
|
filename = loader.get_filename(module) # type: ignore
|
|
else:
|
|
__import__(name)
|
|
filename = sys.modules[name].__file__
|
|
package_path = Path(filename).resolve().parent
|
|
if hasattr(loader, 'is_package'):
|
|
is_package = loader.is_package(module) # type: ignore
|
|
if is_package:
|
|
package_path = Path(package_path).resolve().parent
|
|
sys_prefix = Path(sys.prefix).resolve()
|
|
try:
|
|
package_path.relative_to(sys_prefix)
|
|
except ValueError:
|
|
return None, package_path
|
|
else:
|
|
return sys_prefix, package_path
|