Compare commits
1 commit
main
...
automatic-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80669a0ea8 |
9 changed files with 438 additions and 464 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: 5e2fb545eba1ea9dc051f6f962d52fe8f76a9794 # frozen: v0.15.13
|
rev: c60c980e561ed3e73101667fe8365c609d19a438 # frozen: v0.15.9
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
rev: fa60a193803535a9e2accdb3ca4b1b584b1150cb # frozen: 0.11.15
|
rev: 0397b68f6f88c024f1d2b355a9818779f6336d16 # frozen: 0.11.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: uv-lock
|
- id: uv-lock
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ A running MongoDB server and `Flask-MongoEngine`_ are required. ::
|
||||||
pip install flask-mongoengine
|
pip install flask-mongoengine
|
||||||
|
|
||||||
.. _MongoEngine: http://mongoengine.org
|
.. _MongoEngine: http://mongoengine.org
|
||||||
.. _Flask-MongoEngine: https://docs.mongoengine.org/projects/flask-mongoengine/en/latest/
|
.. _Flask-MongoEngine: https://flask-mongoengine.readthedocs.io
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ By the end, your project layout will look like this:
|
||||||
│ ├── test_auth.py
|
│ ├── test_auth.py
|
||||||
│ └── test_blog.py
|
│ └── test_blog.py
|
||||||
├── .venv/
|
├── .venv/
|
||||||
└── pyproject.toml
|
├── pyproject.toml
|
||||||
|
└── MANIFEST.in
|
||||||
|
|
||||||
If you're using version control, the following files that are generated
|
If you're using version control, the following files that are generated
|
||||||
while running your project should be ignored. There may be other files
|
while running your project should be ignored. There may be other files
|
||||||
|
|
@ -102,4 +103,8 @@ write. For example, with git:
|
||||||
.coverage
|
.coverage
|
||||||
htmlcov/
|
htmlcov/
|
||||||
|
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
Continue to :doc:`factory`.
|
Continue to :doc:`factory`.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import inspect
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import typing as t
|
import typing as t
|
||||||
import weakref
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from inspect import iscoroutinefunction
|
from inspect import iscoroutinefunction
|
||||||
|
|
@ -352,16 +351,15 @@ class Flask(App):
|
||||||
assert bool(static_host) == host_matching, (
|
assert bool(static_host) == host_matching, (
|
||||||
"Invalid static_host/host_matching combination"
|
"Invalid static_host/host_matching combination"
|
||||||
)
|
)
|
||||||
# Use a weakref to avoid creating a reference cycle between the app
|
|
||||||
# and the view function (see #3761).
|
|
||||||
self_ref = weakref.ref(self)
|
|
||||||
self.add_url_rule(
|
self.add_url_rule(
|
||||||
f"{self.static_url_path}/<path:filename>",
|
f"{self.static_url_path}/<path:filename>",
|
||||||
endpoint="static",
|
endpoint="static",
|
||||||
host=static_host,
|
host=static_host,
|
||||||
view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore
|
view_func=_static_view,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.view_functions["_automatic_options"] = _options_view
|
||||||
|
|
||||||
def get_send_file_max_age(self, filename: str | None) -> int | None:
|
def get_send_file_max_age(self, filename: str | None) -> int | None:
|
||||||
"""Used by :func:`send_file` to determine the ``max_age`` cache
|
"""Used by :func:`send_file` to determine the ``max_age`` cache
|
||||||
value for a given file path if it wasn't passed.
|
value for a given file path if it wasn't passed.
|
||||||
|
|
@ -978,14 +976,6 @@ class Flask(App):
|
||||||
if req.routing_exception is not None:
|
if req.routing_exception is not None:
|
||||||
self.raise_routing_exception(req)
|
self.raise_routing_exception(req)
|
||||||
rule: Rule = req.url_rule # type: ignore[assignment]
|
rule: Rule = req.url_rule # type: ignore[assignment]
|
||||||
# if we provide automatic options for this URL and the
|
|
||||||
# request came with the OPTIONS method, reply automatically
|
|
||||||
if (
|
|
||||||
getattr(rule, "provide_automatic_options", False)
|
|
||||||
and req.method == "OPTIONS"
|
|
||||||
):
|
|
||||||
return self.make_default_options_response(ctx)
|
|
||||||
# otherwise dispatch to the handler for that endpoint
|
|
||||||
view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
|
view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
|
||||||
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
|
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
@ -1623,3 +1613,12 @@ class Flask(App):
|
||||||
wrapped to apply middleware.
|
wrapped to apply middleware.
|
||||||
"""
|
"""
|
||||||
return self.wsgi_app(environ, start_response)
|
return self.wsgi_app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
def _static_view(filename: str) -> Response:
|
||||||
|
return app_ctx.app.send_static_file(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _options_view(**_: t.Any) -> Response:
|
||||||
|
ctx = app_ctx._get_current_object()
|
||||||
|
return ctx.app.make_default_options_response(ctx)
|
||||||
|
|
|
||||||
|
|
@ -468,7 +468,7 @@ _app_option = click.Option(
|
||||||
def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
|
def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
|
||||||
# If the flag isn't provided, it will default to False. Don't use
|
# If the flag isn't provided, it will default to False. Don't use
|
||||||
# that, let debug be set by env in that case.
|
# that, let debug be set by env in that case.
|
||||||
source = ctx.get_parameter_source(param.name)
|
source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
|
||||||
|
|
||||||
if source is not None and source in (
|
if source is not None and source in (
|
||||||
ParameterSource.DEFAULT,
|
ParameterSource.DEFAULT,
|
||||||
|
|
@ -777,7 +777,7 @@ def show_server_banner(debug: bool, app_import_path: str | None) -> None:
|
||||||
click.echo(f" * Debug mode: {'on' if debug else 'off'}")
|
click.echo(f" * Debug mode: {'on' if debug else 'off'}")
|
||||||
|
|
||||||
|
|
||||||
class CertParamType(click.ParamType[t.Any]):
|
class CertParamType(click.ParamType):
|
||||||
"""Click option type for the ``--cert`` option. Allows either an
|
"""Click option type for the ``--cert`` option. Allows either an
|
||||||
existing file, the string ``'adhoc'``, or an import for a
|
existing file, the string ``'adhoc'``, or an import for a
|
||||||
:class:`~ssl.SSLContext` object.
|
:class:`~ssl.SSLContext` object.
|
||||||
|
|
@ -803,7 +803,7 @@ class CertParamType(click.ParamType[t.Any]):
|
||||||
try:
|
try:
|
||||||
return self.path_type(value, param, ctx)
|
return self.path_type(value, param, ctx)
|
||||||
except click.BadParameter:
|
except click.BadParameter:
|
||||||
value = click.STRING(value, param, ctx).lower() # type: ignore[union-attr]
|
value = click.STRING(value, param, ctx).lower()
|
||||||
|
|
||||||
if value == "adhoc":
|
if value == "adhoc":
|
||||||
try:
|
try:
|
||||||
|
|
@ -1072,10 +1072,15 @@ def routes_command(sort: str, all_methods: bool) -> None:
|
||||||
rows = []
|
rows = []
|
||||||
|
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
row = [
|
if rule.endpoint == "_automatic_options":
|
||||||
rule.endpoint,
|
continue
|
||||||
", ".join(sorted((rule.methods or set()) - ignored_methods)),
|
|
||||||
]
|
methods = rule.methods or set()
|
||||||
|
|
||||||
|
if getattr(rule, "provide_automatic_options", False):
|
||||||
|
methods.add("OPTIONS")
|
||||||
|
|
||||||
|
row = [rule.endpoint, ", ".join(sorted(methods - ignored_methods))]
|
||||||
|
|
||||||
if has_domain:
|
if has_domain:
|
||||||
row.append((rule.host if host_matching else rule.subdomain) or "")
|
row.append((rule.host if host_matching else rule.subdomain) or "")
|
||||||
|
|
|
||||||
|
|
@ -612,7 +612,7 @@ class App(Scaffold):
|
||||||
) -> None:
|
) -> None:
|
||||||
if endpoint is None:
|
if endpoint is None:
|
||||||
endpoint = _endpoint_from_view_func(view_func) # type: ignore
|
endpoint = _endpoint_from_view_func(view_func) # type: ignore
|
||||||
options["endpoint"] = endpoint
|
|
||||||
methods = options.pop("methods", None)
|
methods = options.pop("methods", None)
|
||||||
|
|
||||||
# if the methods are not given and the view_func object knows its
|
# if the methods are not given and the view_func object knows its
|
||||||
|
|
@ -620,15 +620,16 @@ class App(Scaffold):
|
||||||
# a tuple of only ``GET`` as default.
|
# a tuple of only ``GET`` as default.
|
||||||
if methods is None:
|
if methods is None:
|
||||||
methods = getattr(view_func, "methods", None) or ("GET",)
|
methods = getattr(view_func, "methods", None) or ("GET",)
|
||||||
|
|
||||||
if isinstance(methods, str):
|
if isinstance(methods, str):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Allowed methods must be a list of strings, for"
|
"Allowed methods must be a list of strings, for"
|
||||||
' example: @app.route(..., methods=["POST"])'
|
' example: @app.route(..., methods=["POST"])'
|
||||||
)
|
)
|
||||||
methods = {item.upper() for item in methods}
|
|
||||||
|
|
||||||
|
methods = {item.upper() for item in methods}
|
||||||
# Methods that should always be added
|
# Methods that should always be added
|
||||||
required_methods: set[str] = set(getattr(view_func, "required_methods", ()))
|
methods |= set(getattr(view_func, "required_methods", ()))
|
||||||
|
|
||||||
if provide_automatic_options is None:
|
if provide_automatic_options is None:
|
||||||
provide_automatic_options = getattr(
|
provide_automatic_options = getattr(
|
||||||
|
|
@ -641,16 +642,12 @@ class App(Scaffold):
|
||||||
and self.config["PROVIDE_AUTOMATIC_OPTIONS"]
|
and self.config["PROVIDE_AUTOMATIC_OPTIONS"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if provide_automatic_options:
|
rule_obj = self.url_rule_class(
|
||||||
required_methods.add("OPTIONS")
|
rule, methods=methods, endpoint=endpoint, **options
|
||||||
|
)
|
||||||
# Add the required methods now.
|
|
||||||
methods |= required_methods
|
|
||||||
|
|
||||||
rule_obj = self.url_rule_class(rule, methods=methods, **options)
|
|
||||||
rule_obj.provide_automatic_options = provide_automatic_options # type: ignore[attr-defined]
|
rule_obj.provide_automatic_options = provide_automatic_options # type: ignore[attr-defined]
|
||||||
|
|
||||||
self.url_map.add(rule_obj)
|
self.url_map.add(rule_obj)
|
||||||
|
|
||||||
if view_func is not None:
|
if view_func is not None:
|
||||||
old_func = self.view_functions.get(endpoint)
|
old_func = self.view_functions.get(endpoint)
|
||||||
if old_func is not None and old_func != view_func:
|
if old_func is not None and old_func != view_func:
|
||||||
|
|
@ -660,6 +657,19 @@ class App(Scaffold):
|
||||||
)
|
)
|
||||||
self.view_functions[endpoint] = view_func
|
self.view_functions[endpoint] = view_func
|
||||||
|
|
||||||
|
if provide_automatic_options:
|
||||||
|
try:
|
||||||
|
self.url_map.add(
|
||||||
|
self.url_rule_class(
|
||||||
|
rule,
|
||||||
|
methods={"OPTIONS"},
|
||||||
|
endpoint="_automatic_options",
|
||||||
|
**options,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
@t.overload
|
@t.overload
|
||||||
def template_filter(self, name: T_template_filter) -> T_template_filter: ...
|
def template_filter(self, name: T_template_filter) -> T_template_filter: ...
|
||||||
@t.overload
|
@t.overload
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,13 @@ def test_options_on_multiple_rules(app, client):
|
||||||
assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST", "PUT"]
|
assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST", "PUT"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_options_view_args(app: flask.Flask, client: FlaskClient) -> None:
|
||||||
|
"""The automatic options view accepts any view args."""
|
||||||
|
app.add_url_rule("/<a>/<b>", endpoint="add")
|
||||||
|
rv = client.options("/1/2")
|
||||||
|
assert rv.allow == {"GET", "HEAD", "OPTIONS"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("method", ["get", "post", "put", "delete", "patch"])
|
@pytest.mark.parametrize("method", ["get", "post", "put", "delete", "patch"])
|
||||||
def test_method_route(app, client, method):
|
def test_method_route(app, client, method):
|
||||||
method_route = getattr(app, method)
|
method_route = getattr(app, method)
|
||||||
|
|
|
||||||
|
|
@ -483,12 +483,17 @@ class TestRoutes:
|
||||||
["yyy_get_post", "static", "aaa_post"],
|
["yyy_get_post", "static", "aaa_post"],
|
||||||
invoke(["routes", "-s", "rule"]).output,
|
invoke(["routes", "-s", "rule"]).output,
|
||||||
)
|
)
|
||||||
match_order = [r.endpoint for r in app.url_map.iter_rules()]
|
match_order = [
|
||||||
|
r.endpoint
|
||||||
|
for r in app.url_map.iter_rules()
|
||||||
|
if r.endpoint != "_automatic_options"
|
||||||
|
]
|
||||||
self.expect_order(match_order, invoke(["routes", "-s", "match"]).output)
|
self.expect_order(match_order, invoke(["routes", "-s", "match"]).output)
|
||||||
|
|
||||||
def test_all_methods(self, invoke):
|
def test_all_methods(self, invoke):
|
||||||
output = invoke(["routes"]).output
|
output = invoke(["routes"]).output
|
||||||
assert "GET, HEAD, OPTIONS, POST" not in output
|
assert "HEAD" not in output
|
||||||
|
assert "OPTIONS" not in output
|
||||||
|
|
||||||
output = invoke(["routes", "--all-methods"]).output
|
output = invoke(["routes", "--all-methods"]).output
|
||||||
assert "GET, HEAD, OPTIONS, POST" in output
|
assert "GET, HEAD, OPTIONS, POST" in output
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue