Skip to content

Commit

Permalink
Split the App and Blueprint into Sansio and IO parts
Browse files Browse the repository at this point in the history
This follows a similar structure in Werkzeug and allows for async
based IO projects, specifically Quart, to base themselves on
Flask.

Note that the globals, and signals are specific to Flask and hence
specific to Flask's IO. This means they cannot be moved to the sansio
part of the codebase.
  • Loading branch information
pgjones committed Jun 11, 2023
1 parent e399b29 commit 194b16d
Show file tree
Hide file tree
Showing 11 changed files with 1,451 additions and 1,182 deletions.
1,306 changes: 1,306 additions & 0 deletions src/flask/app.py

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions src/flask/blueprints.py
@@ -0,0 +1,91 @@
from __future__ import annotations

import os
import typing as t
from datetime import timedelta

from .globals import current_app
from .helpers import send_from_directory
from .sansio.blueprints import Blueprint as SansioBlueprint
from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa

if t.TYPE_CHECKING: # pragma: no cover
from .wrappers import Response


class Blueprint(SansioBlueprint):
def get_send_file_max_age(self, filename: str | None) -> int | None:
"""Used by :func:`send_file` to determine the ``max_age`` cache
value for a given file path if it wasn't passed.
By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
the configuration of :data:`~flask.current_app`. This defaults
to ``None``, which tells the browser to use conditional requests
instead of a timed cache, which is usually preferable.
Note this is a duplicate of the same method in the Flask
class.
.. versionchanged:: 2.0
The default configuration is ``None`` instead of 12 hours.
.. versionadded:: 0.9
"""
value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]

if value is None:
return None

if isinstance(value, timedelta):
return int(value.total_seconds())

return value

def send_static_file(self, filename: str) -> Response:
"""The view function used to serve files from
:attr:`static_folder`. A route is automatically registered for
this view at :attr:`static_url_path` if :attr:`static_folder` is
set.
Note this is a duplicate of the same method in the Flask
class.
.. versionadded:: 0.5
"""
if not self.has_static_folder:
raise RuntimeError("'static_folder' must be set to serve static_files.")

# send_file only knows to call get_send_file_max_age on the app,
# call it here so it works for blueprints too.
max_age = self.get_send_file_max_age(filename)
return send_from_directory(
t.cast(str, self.static_folder), filename, max_age=max_age
)

def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
"""Open a resource file relative to :attr:`root_path` for
reading.
For example, if the file ``schema.sql`` is next to the file
``app.py`` where the ``Flask`` app is defined, it can be opened
with:
.. code-block:: python
with app.open_resource("schema.sql") as f:
conn.executescript(f.read())
:param resource: Path to the resource relative to
:attr:`root_path`.
:param mode: Open the file in this mode. Only reading is
supported, valid values are "r" (or "rt") and "rb".
Note this is a duplicate of the same method in the Flask
class.
"""
if mode not in {"r", "rt", "rb"}:
raise ValueError("Resources can only be opened for reading.")

return open(os.path.join(self.root_path, resource), mode)
6 changes: 3 additions & 3 deletions src/flask/debughelpers.py
Expand Up @@ -2,9 +2,9 @@

import typing as t

from .app import Flask
from .blueprints import Blueprint
from .globals import request_ctx
from .sansio.app import App


class UnexpectedUnicodeError(AssertionError, UnicodeError):
Expand Down Expand Up @@ -113,7 +113,7 @@ def _dump_loader_info(loader) -> t.Generator:
yield f"{key}: {value!r}"


def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
def explain_template_loading_attempts(app: App, template, attempts) -> None:
"""This should help developers understand what failed"""
info = [f"Locating template {template!r}:"]
total_found = 0
Expand All @@ -122,7 +122,7 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
blueprint = request_ctx.request.blueprint

for idx, (loader, srcobj, triple) in enumerate(attempts):
if isinstance(srcobj, Flask):
if isinstance(srcobj, App):
src_info = f"application {srcobj.import_name!r}"
elif isinstance(srcobj, Blueprint):
src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
Expand Down
4 changes: 2 additions & 2 deletions src/flask/json/provider.py
Expand Up @@ -11,7 +11,7 @@
from werkzeug.http import http_date

if t.TYPE_CHECKING: # pragma: no cover
from ..app import Flask
from ..sansio.app import App
from ..wrappers import Response


Expand All @@ -34,7 +34,7 @@ class and implement at least :meth:`dumps` and :meth:`loads`. All
.. versionadded:: 2.2
"""

def __init__(self, app: Flask) -> None:
def __init__(self, app: App) -> None:
self._app = weakref.proxy(app)

def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
Expand Down
4 changes: 2 additions & 2 deletions src/flask/logging.py
Expand Up @@ -9,7 +9,7 @@
from .globals import request

if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
from .sansio.app import App


@LocalProxy
Expand Down Expand Up @@ -52,7 +52,7 @@ def has_level_handler(logger: logging.Logger) -> bool:
)


def create_logger(app: Flask) -> logging.Logger:
def create_logger(app: App) -> logging.Logger:
"""Get the Flask app's logger and configure it if needed.
The logger name will be the same as
Expand Down

0 comments on commit 194b16d

Please sign in to comment.