forked from orbit-oss/flask
Split the App and Blueprint into Sansio and IO parts
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.
This commit is contained in:
parent
a64588f87a
commit
0ec7f713d6
10 changed files with 1621 additions and 1352 deletions
|
|
@ -1,7 +1,5 @@
|
|||
from . import json as json
|
||||
from .app import Flask as Flask
|
||||
from .app import Request as Request
|
||||
from .app import Response as Response
|
||||
from .blueprints import Blueprint as Blueprint
|
||||
from .config import Config as Config
|
||||
from .ctx import after_this_request as after_this_request
|
||||
|
|
@ -37,5 +35,7 @@ from .templating import render_template as render_template
|
|||
from .templating import render_template_string as render_template_string
|
||||
from .templating import stream_template as stream_template
|
||||
from .templating import stream_template_string as stream_template_string
|
||||
from .wrappers import Request as Request
|
||||
from .wrappers import Response as Response
|
||||
|
||||
__version__ = "3.0.0.dev"
|
||||
|
|
|
|||
1478
src/flask/app.py
Normal file
1478
src/flask/app.py
Normal file
File diff suppressed because it is too large
Load diff
91
src/flask/blueprints.py
Normal file
91
src/flask/blueprints.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -2,9 +2,9 @@ from __future__ import annotations
|
|||
|
||||
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):
|
||||
|
|
@ -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
|
||||
|
|
@ -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})"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from datetime import date
|
|||
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
|
||||
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ class JSONProvider:
|
|||
.. 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:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from werkzeug.local import LocalProxy
|
|||
from .globals import request
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from .app import Flask
|
||||
from .sansio.app import App
|
||||
|
||||
|
||||
@LocalProxy
|
||||
|
|
@ -52,7 +52,7 @@ default_handler.setFormatter(
|
|||
)
|
||||
|
||||
|
||||
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
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,14 +5,14 @@ import typing as t
|
|||
from collections import defaultdict
|
||||
from functools import update_wrapper
|
||||
|
||||
from . import typing as ft
|
||||
from .. import typing as ft
|
||||
from .scaffold import _endpoint_from_view_func
|
||||
from .scaffold import _sentinel
|
||||
from .scaffold import Scaffold
|
||||
from .scaffold import setupmethod
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from .app import Flask
|
||||
from .app import App
|
||||
|
||||
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
|
||||
T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
|
||||
|
|
@ -41,7 +41,7 @@ class BlueprintSetupState:
|
|||
def __init__(
|
||||
self,
|
||||
blueprint: Blueprint,
|
||||
app: Flask,
|
||||
app: App,
|
||||
options: t.Any,
|
||||
first_registration: bool,
|
||||
) -> None:
|
||||
|
|
@ -244,7 +244,7 @@ class Blueprint(Scaffold):
|
|||
self.record(update_wrapper(wrapper, func))
|
||||
|
||||
def make_setup_state(
|
||||
self, app: Flask, options: dict, first_registration: bool = False
|
||||
self, app: App, options: dict, first_registration: bool = False
|
||||
) -> BlueprintSetupState:
|
||||
"""Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
|
||||
object that is later passed to the register callback functions.
|
||||
|
|
@ -270,7 +270,7 @@ class Blueprint(Scaffold):
|
|||
raise ValueError("Cannot register a blueprint on itself")
|
||||
self._blueprints.append((blueprint, options))
|
||||
|
||||
def register(self, app: Flask, options: dict) -> None:
|
||||
def register(self, app: App, options: dict) -> None:
|
||||
"""Called by :meth:`Flask.register_blueprint` to register all
|
||||
views and callbacks registered on the blueprint with the
|
||||
application. Creates a :class:`.BlueprintSetupState` and calls
|
||||
|
|
@ -323,7 +323,7 @@ class Blueprint(Scaffold):
|
|||
if self.has_static_folder:
|
||||
state.add_url_rule(
|
||||
f"{self.static_url_path}/<path:filename>",
|
||||
view_func=self.send_static_file,
|
||||
view_func=self.send_static_file, # type: ignore[attr-defined]
|
||||
endpoint="static",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import pathlib
|
|||
import sys
|
||||
import typing as t
|
||||
from collections import defaultdict
|
||||
from datetime import timedelta
|
||||
from functools import update_wrapper
|
||||
|
||||
from jinja2 import FileSystemLoader
|
||||
|
|
@ -14,15 +13,10 @@ from werkzeug.exceptions import default_exceptions
|
|||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.utils import cached_property
|
||||
|
||||
from . import typing as ft
|
||||
from .cli import AppGroup
|
||||
from .globals import current_app
|
||||
from .helpers import get_root_path
|
||||
from .helpers import send_from_directory
|
||||
from .templating import _default_template_ctx_processor
|
||||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from .wrappers import Response
|
||||
from .. import typing as ft
|
||||
from ..cli import AppGroup
|
||||
from ..helpers import get_root_path
|
||||
from ..templating import _default_template_ctx_processor
|
||||
|
||||
# a singleton sentinel value for parameter defaults
|
||||
_sentinel = object()
|
||||
|
|
@ -276,48 +270,6 @@ class Scaffold:
|
|||
|
||||
self._static_url_path = value
|
||||
|
||||
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.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. 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
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def jinja_loader(self) -> FileSystemLoader | None:
|
||||
"""The Jinja loader for this object's templates. By default this
|
||||
|
|
@ -331,29 +283,6 @@ class Scaffold:
|
|||
else:
|
||||
return None
|
||||
|
||||
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".
|
||||
"""
|
||||
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)
|
||||
|
||||
def _method_route(
|
||||
self,
|
||||
method: str,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ from .signals import template_rendered
|
|||
|
||||
if t.TYPE_CHECKING: # pragma: no cover
|
||||
from .app import Flask
|
||||
from .scaffold import Scaffold
|
||||
from .sansio.app import App
|
||||
from .sansio.scaffold import Scaffold
|
||||
|
||||
|
||||
def _default_template_ctx_processor() -> dict[str, t.Any]:
|
||||
|
|
@ -41,7 +42,7 @@ class Environment(BaseEnvironment):
|
|||
name of the blueprint to referenced templates if necessary.
|
||||
"""
|
||||
|
||||
def __init__(self, app: Flask, **options: t.Any) -> None:
|
||||
def __init__(self, app: App, **options: t.Any) -> None:
|
||||
if "loader" not in options:
|
||||
options["loader"] = app.create_global_jinja_loader()
|
||||
BaseEnvironment.__init__(self, **options)
|
||||
|
|
@ -53,7 +54,7 @@ class DispatchingJinjaLoader(BaseLoader):
|
|||
the blueprint folders.
|
||||
"""
|
||||
|
||||
def __init__(self, app: Flask) -> None:
|
||||
def __init__(self, app: App) -> None:
|
||||
self.app = app
|
||||
|
||||
def get_source( # type: ignore
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue