Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
flockwave-server / server / utils / quart.py
Size: Mime:
from __future__ import annotations

import os

from datetime import datetime, timedelta, timezone
from pathlib import Path
from sys import executable
from typing import Optional
from zlib import adler32

from jinja2 import BaseLoader, ChoiceLoader, FileSystemLoader, TemplateNotFound
from quart import Blueprint, Response, current_app, send_file
from werkzeug.utils import cached_property

from .generic import constant
from .packaging import is_oxidized


def _get_quart_root_path_of(name: str) -> Optional[str]:
    if is_oxidized():
        # Running inside PyOxidizer, return the current folder as a dummy
        # root path for Quart
        return str(Path.cwd())
    else:
        # Running as a "normal" Python application, return None and let
        # Quart sort it out
        return None


_always_true = constant(True)


class PyOxidizerTemplateLoader(BaseLoader):
    """Jinja2 template loader that looks for a template with the given name in
    a given Python package under a subpath.
    """

    def __init__(self, package: str, path: str = "templates", encoding: str = "utf-8"):
        self.package = package
        self.path = path

    def get_source(self, environment, template):
        reader = __loader__.get_resource_reader(self.package)
        if reader is None:
            raise TemplateNotFound(template)
        try:
            # __loader__ points to PyOxidizer's OxidizedFinder
            with reader.open_resource(f"{self.path}/{template}") as fp:
                data = fp.read().decode("utf-8")
        except Exception:
            raise TemplateNotFound(template) from None

        return (data, None, _always_true)


class PyOxidizerBlueprint(Blueprint):
    """PyOxidizer-compatible Quart blueprint class that resolves static assets
    from the PyOxidizer package before falling back to resolving them from the
    filesystem.
    """

    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)

        # Store the timestamp of the main executable. Static files loaded from
        # the executable will return this timestamp as their last-modified
        # timestamp

    # jinja_loader getter copied from flask.sansio.scaffold as we cannot override
    # a cached property since Werkzeug 3.x for some reason

    @cached_property
    def jinja_loader(self) -> BaseLoader:
        package, _, _ = self.import_name.rpartition(".")
        oxidized_loader = PyOxidizerTemplateLoader(package)
        if self.template_folder is not None:
            super_loader = FileSystemLoader(
                os.path.join(self.root_path, self.template_folder)
            )
        else:
            super_loader = None
        if super_loader:
            return ChoiceLoader([oxidized_loader, super_loader])
        else:
            return oxidized_loader

    async def send_static_file(self, filename: str) -> Response:
        if not self.static_folder:
            raise RuntimeError("No static folder for this object")

        # __loader__ points to PyOxidizer's OxidizedFinder
        package, _, _ = self.import_name.rpartition(".")
        reader = __loader__.get_resource_reader(package)
        resource_path = f"static/{filename}"

        try:
            if reader is not None:
                data = reader.open_resource(resource_path)
            else:
                data = None
        except Exception:
            data = None

        if not data:
            # Resource not embedded; fall back to static file retrieval
            return await super().send_static_file(filename)

        assert reader is not None

        # Do not use a with block to close data; it must be kept open until
        # the request handler ends
        response: Response = await send_file(
            data, attachment_filename=Path(filename).name
        )

        # Check whether the file is represented by a physical file on the disk
        try:
            path = Path(reader.resource_path(resource_path))
        except Exception:
            path = None

        # If the file is not represented by a physical file on the disk, use the
        # executable of the bundled app
        if not path or not path.is_file():
            path = Path(executable)

        if path and path.is_file():
            # Set ETag and Last-Modified headers
            stat = path.stat()
            etag = "{}-{}-{}".format(stat.st_mtime, stat.st_size, adler32(bytes(path)))
            response.set_etag(etag)
            response.last_modified = datetime.fromtimestamp(stat.st_mtime)

        if path:
            # Set max age and expiry date for caching
            cache_timeout = current_app.get_send_file_max_age(str(path))
            if cache_timeout is not None:
                response.cache_control.max_age = cache_timeout
                response.expires = datetime.now(timezone.utc) + timedelta(
                    seconds=cache_timeout
                )

        return response


def make_blueprint(name, import_name, *args, **kwds) -> Blueprint:
    """Creates a Quart blueprint that takes into account whether we are running
    in a PyOxidizer-enabled distribution or not.
    """
    if "root_path" not in kwds:
        kwds["root_path"] = _get_quart_root_path_of(name)

    cls = PyOxidizerBlueprint if is_oxidized() else Blueprint
    return cls(name, import_name, *args, **kwds)