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 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