Merge remote-tracking branch 'origin/2.0.x'
This commit is contained in:
commit
4240ace597
14 changed files with 202 additions and 163 deletions
|
|
@ -1,46 +1,46 @@
|
|||
from markupsafe import escape
|
||||
from markupsafe import Markup
|
||||
from werkzeug.exceptions import abort
|
||||
from werkzeug.utils import redirect
|
||||
from werkzeug.exceptions import abort as abort
|
||||
from werkzeug.utils import redirect as redirect
|
||||
|
||||
from . import json
|
||||
from .app import Flask
|
||||
from .app import Request
|
||||
from .app import Response
|
||||
from .blueprints import Blueprint
|
||||
from .config import Config
|
||||
from .ctx import after_this_request
|
||||
from .ctx import copy_current_request_context
|
||||
from .ctx import has_app_context
|
||||
from .ctx import has_request_context
|
||||
from .globals import _app_ctx_stack
|
||||
from .globals import _request_ctx_stack
|
||||
from .globals import current_app
|
||||
from .globals import g
|
||||
from .globals import request
|
||||
from .globals import session
|
||||
from .helpers import flash
|
||||
from .helpers import get_flashed_messages
|
||||
from .helpers import get_template_attribute
|
||||
from .helpers import make_response
|
||||
from .helpers import safe_join
|
||||
from .helpers import send_file
|
||||
from .helpers import send_from_directory
|
||||
from .helpers import stream_with_context
|
||||
from .helpers import url_for
|
||||
from .json import jsonify
|
||||
from .signals import appcontext_popped
|
||||
from .signals import appcontext_pushed
|
||||
from .signals import appcontext_tearing_down
|
||||
from .signals import before_render_template
|
||||
from .signals import got_request_exception
|
||||
from .signals import message_flashed
|
||||
from .signals import request_finished
|
||||
from .signals import request_started
|
||||
from .signals import request_tearing_down
|
||||
from .signals import signals_available
|
||||
from .signals import template_rendered
|
||||
from .templating import render_template
|
||||
from .templating import render_template_string
|
||||
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
|
||||
from .ctx import copy_current_request_context as copy_current_request_context
|
||||
from .ctx import has_app_context as has_app_context
|
||||
from .ctx import has_request_context as has_request_context
|
||||
from .globals import _app_ctx_stack as _app_ctx_stack
|
||||
from .globals import _request_ctx_stack as _request_ctx_stack
|
||||
from .globals import current_app as current_app
|
||||
from .globals import g as g
|
||||
from .globals import request as request
|
||||
from .globals import session as session
|
||||
from .helpers import flash as flash
|
||||
from .helpers import get_flashed_messages as get_flashed_messages
|
||||
from .helpers import get_template_attribute as get_template_attribute
|
||||
from .helpers import make_response as make_response
|
||||
from .helpers import safe_join as safe_join
|
||||
from .helpers import send_file as send_file
|
||||
from .helpers import send_from_directory as send_from_directory
|
||||
from .helpers import stream_with_context as stream_with_context
|
||||
from .helpers import url_for as url_for
|
||||
from .json import jsonify as jsonify
|
||||
from .signals import appcontext_popped as appcontext_popped
|
||||
from .signals import appcontext_pushed as appcontext_pushed
|
||||
from .signals import appcontext_tearing_down as appcontext_tearing_down
|
||||
from .signals import before_render_template as before_render_template
|
||||
from .signals import got_request_exception as got_request_exception
|
||||
from .signals import message_flashed as message_flashed
|
||||
from .signals import request_finished as request_finished
|
||||
from .signals import request_started as request_started
|
||||
from .signals import request_tearing_down as request_tearing_down
|
||||
from .signals import signals_available as signals_available
|
||||
from .signals import template_rendered as template_rendered
|
||||
from .templating import render_template as render_template
|
||||
from .templating import render_template_string as render_template_string
|
||||
|
||||
__version__ = "2.1.0.dev0"
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ from .wrappers import Request
|
|||
from .wrappers import Response
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
from .blueprints import Blueprint
|
||||
from .testing import FlaskClient
|
||||
from .testing import FlaskCliRunner
|
||||
|
|
@ -1441,7 +1442,7 @@ class Flask(Scaffold):
|
|||
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
|
||||
)
|
||||
|
||||
def raise_routing_exception(self, request: Request) -> t.NoReturn:
|
||||
def raise_routing_exception(self, request: Request) -> "te.NoReturn":
|
||||
"""Exceptions that are recording during routing are reraised with
|
||||
this method. During debug we are not reraising redirect requests
|
||||
for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising
|
||||
|
|
|
|||
|
|
@ -188,6 +188,10 @@ class Blueprint(Scaffold):
|
|||
template_folder=template_folder,
|
||||
root_path=root_path,
|
||||
)
|
||||
|
||||
if "." in name:
|
||||
raise ValueError("'name' may not contain a dot '.' character.")
|
||||
|
||||
self.name = name
|
||||
self.url_prefix = url_prefix
|
||||
self.subdomain = subdomain
|
||||
|
|
@ -256,7 +260,7 @@ class Blueprint(Scaffold):
|
|||
"""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
|
||||
each :meth:`record` callbackwith it.
|
||||
each :meth:`record` callback with it.
|
||||
|
||||
:param app: The application this blueprint is being registered
|
||||
with.
|
||||
|
|
@ -340,13 +344,17 @@ class Blueprint(Scaffold):
|
|||
app.cli.add_command(self.cli)
|
||||
|
||||
for blueprint, bp_options in self._blueprints:
|
||||
url_prefix = options.get("url_prefix", "")
|
||||
if "url_prefix" in bp_options:
|
||||
url_prefix = (
|
||||
url_prefix.rstrip("/") + "/" + bp_options["url_prefix"].lstrip("/")
|
||||
bp_options = bp_options.copy()
|
||||
bp_url_prefix = bp_options.get("url_prefix")
|
||||
|
||||
if bp_url_prefix is None:
|
||||
bp_url_prefix = blueprint.url_prefix
|
||||
|
||||
if state.url_prefix is not None and bp_url_prefix is not None:
|
||||
bp_options["url_prefix"] = (
|
||||
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
|
||||
)
|
||||
|
||||
bp_options["url_prefix"] = url_prefix
|
||||
bp_options["name_prefix"] = options.get("name_prefix", "") + self.name + "."
|
||||
blueprint.register(app, bp_options)
|
||||
|
||||
|
|
@ -360,12 +368,12 @@ class Blueprint(Scaffold):
|
|||
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
||||
the :func:`url_for` function is prefixed with the name of the blueprint.
|
||||
"""
|
||||
if endpoint:
|
||||
assert "." not in endpoint, "Blueprint endpoints should not contain dots"
|
||||
if view_func and hasattr(view_func, "__name__"):
|
||||
assert (
|
||||
"." not in view_func.__name__
|
||||
), "Blueprint view function name should not contain dots"
|
||||
if endpoint and "." in endpoint:
|
||||
raise ValueError("'endpoint' may not contain a dot '.' character.")
|
||||
|
||||
if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
|
||||
raise ValueError("'view_func' name may not contain a dot '.' character.")
|
||||
|
||||
self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
|
||||
|
||||
def app_template_filter(self, name: t.Optional[str] = None) -> t.Callable:
|
||||
|
|
|
|||
|
|
@ -41,6 +41,24 @@ class _AppCtxGlobals:
|
|||
.. versionadded:: 0.10
|
||||
"""
|
||||
|
||||
# Define attr methods to let mypy know this is a namespace object
|
||||
# that has arbitrary attributes.
|
||||
|
||||
def __getattr__(self, name: str) -> t.Any:
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name) from None
|
||||
|
||||
def __setattr__(self, name: str, value: t.Any) -> None:
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __delattr__(self, name: str) -> None:
|
||||
try:
|
||||
del self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name) from None
|
||||
|
||||
def get(self, name: str, default: t.Optional[t.Any] = None) -> t.Any:
|
||||
"""Get an attribute by name, or a default value. Like
|
||||
:meth:`dict.get`.
|
||||
|
|
@ -78,10 +96,10 @@ class _AppCtxGlobals:
|
|||
"""
|
||||
return self.__dict__.setdefault(name, default)
|
||||
|
||||
def __contains__(self, item: t.Any) -> bool:
|
||||
def __contains__(self, item: str) -> bool:
|
||||
return item in self.__dict__
|
||||
|
||||
def __iter__(self) -> t.Iterator:
|
||||
def __iter__(self) -> t.Iterator[str]:
|
||||
return iter(self.__dict__)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from werkzeug.local import LocalStack
|
|||
|
||||
if t.TYPE_CHECKING:
|
||||
from .app import Flask
|
||||
from .ctx import AppContext
|
||||
from .ctx import _AppCtxGlobals
|
||||
from .sessions import SessionMixin
|
||||
from .wrappers import Request
|
||||
|
||||
|
|
@ -53,5 +53,7 @@ _request_ctx_stack = LocalStack()
|
|||
_app_ctx_stack = LocalStack()
|
||||
current_app: "Flask" = LocalProxy(_find_app) # type: ignore
|
||||
request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore
|
||||
session: "SessionMixin" = LocalProxy(partial(_lookup_req_object, "session")) # type: ignore # noqa: B950
|
||||
g: "AppContext" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore
|
||||
session: "SessionMixin" = LocalProxy( # type: ignore
|
||||
partial(_lookup_req_object, "session")
|
||||
)
|
||||
g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import socket
|
|||
import sys
|
||||
import typing as t
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from functools import update_wrapper
|
||||
from threading import RLock
|
||||
|
|
@ -436,14 +437,16 @@ def get_flashed_messages(
|
|||
|
||||
|
||||
def _prepare_send_file_kwargs(
|
||||
download_name=None,
|
||||
attachment_filename=None,
|
||||
etag=None,
|
||||
add_etags=None,
|
||||
max_age=None,
|
||||
cache_timeout=None,
|
||||
**kwargs,
|
||||
):
|
||||
download_name: t.Optional[str] = None,
|
||||
attachment_filename: t.Optional[str] = None,
|
||||
etag: t.Optional[t.Union[bool, str]] = None,
|
||||
add_etags: t.Optional[t.Union[bool]] = None,
|
||||
max_age: t.Optional[
|
||||
t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]]
|
||||
] = None,
|
||||
cache_timeout: t.Optional[int] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Dict[str, t.Any]:
|
||||
if attachment_filename is not None:
|
||||
warnings.warn(
|
||||
"The 'attachment_filename' parameter has been renamed to"
|
||||
|
|
@ -482,23 +485,25 @@ def _prepare_send_file_kwargs(
|
|||
max_age=max_age,
|
||||
use_x_sendfile=current_app.use_x_sendfile,
|
||||
response_class=current_app.response_class,
|
||||
_root_path=current_app.root_path,
|
||||
_root_path=current_app.root_path, # type: ignore
|
||||
)
|
||||
return kwargs
|
||||
|
||||
|
||||
def send_file(
|
||||
path_or_file,
|
||||
mimetype=None,
|
||||
as_attachment=False,
|
||||
download_name=None,
|
||||
attachment_filename=None,
|
||||
conditional=True,
|
||||
etag=True,
|
||||
add_etags=None,
|
||||
last_modified=None,
|
||||
max_age=None,
|
||||
cache_timeout=None,
|
||||
path_or_file: t.Union[os.PathLike, str, t.BinaryIO],
|
||||
mimetype: t.Optional[str] = None,
|
||||
as_attachment: bool = False,
|
||||
download_name: t.Optional[str] = None,
|
||||
attachment_filename: t.Optional[str] = None,
|
||||
conditional: bool = True,
|
||||
etag: t.Union[bool, str] = True,
|
||||
add_etags: t.Optional[bool] = None,
|
||||
last_modified: t.Optional[t.Union[datetime, int, float]] = None,
|
||||
max_age: t.Optional[
|
||||
t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]]
|
||||
] = None,
|
||||
cache_timeout: t.Optional[int] = None,
|
||||
):
|
||||
"""Send the contents of a file to the client.
|
||||
|
||||
|
|
@ -642,7 +647,12 @@ def safe_join(directory: str, *pathnames: str) -> str:
|
|||
return path
|
||||
|
||||
|
||||
def send_from_directory(directory: str, path: str, **kwargs: t.Any) -> "Response":
|
||||
def send_from_directory(
|
||||
directory: t.Union[os.PathLike, str],
|
||||
path: t.Union[os.PathLike, str],
|
||||
filename: t.Optional[str] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> "Response":
|
||||
"""Send a file from within a directory using :func:`send_file`.
|
||||
|
||||
.. code-block:: python
|
||||
|
|
@ -666,12 +676,24 @@ def send_from_directory(directory: str, path: str, **kwargs: t.Any) -> "Response
|
|||
``directory``.
|
||||
:param kwargs: Arguments to pass to :func:`send_file`.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
``path`` replaces the ``filename`` parameter.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
Moved the implementation to Werkzeug. This is now a wrapper to
|
||||
pass some Flask-specific arguments.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
if filename is not None:
|
||||
warnings.warn(
|
||||
"The 'filename' parameter has been renamed to 'path'. The"
|
||||
" old name will be removed in Flask 2.1.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
path = filename
|
||||
|
||||
return werkzeug.utils.send_from_directory( # type: ignore
|
||||
directory, path, **_prepare_send_file_kwargs(**kwargs)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ class Scaffold:
|
|||
|
||||
self._static_url_path = value
|
||||
|
||||
def get_send_file_max_age(self, filename: str) -> t.Optional[int]:
|
||||
def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]:
|
||||
"""Used by :func:`send_file` to determine the ``max_age`` cache
|
||||
value for a given file path if it wasn't passed.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from .helpers import is_ip
|
|||
from .json.tag import TaggedJSONSerializer
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
from .app import Flask
|
||||
from .wrappers import Request, Response
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ class NullSession(SecureCookieSession):
|
|||
but fail on setting.
|
||||
"""
|
||||
|
||||
def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
|
||||
def _fail(self, *args: t.Any, **kwargs: t.Any) -> "te.NoReturn":
|
||||
raise RuntimeError(
|
||||
"The session is unavailable because no secret "
|
||||
"key was set. Set the secret_key on the "
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from . import json
|
|||
from .globals import current_app
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
from werkzeug.routing import Rule
|
||||
|
||||
|
||||
|
|
@ -91,7 +92,7 @@ class Request(RequestBase):
|
|||
|
||||
attach_enctype_error_multidict(self)
|
||||
|
||||
def on_json_loading_failed(self, e: Exception) -> t.NoReturn:
|
||||
def on_json_loading_failed(self, e: Exception) -> "te.NoReturn":
|
||||
if current_app and current_app.debug:
|
||||
raise BadRequest(f"Failed to decode JSON object: {e}")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue