Merge branch 'main' into class-route
This commit is contained in:
commit
e5fb65b171
50 changed files with 902 additions and 534 deletions
|
|
@ -6,7 +6,8 @@ import pytest
|
|||
from flask import Blueprint
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask.helpers import run_async
|
||||
from flask.views import MethodView
|
||||
from flask.views import View
|
||||
|
||||
pytest.importorskip("asgiref")
|
||||
|
||||
|
|
@ -19,6 +20,24 @@ class BlueprintError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class AsyncView(View):
|
||||
methods = ["GET", "POST"]
|
||||
|
||||
async def dispatch_request(self):
|
||||
await asyncio.sleep(0)
|
||||
return request.method
|
||||
|
||||
|
||||
class AsyncMethodView(MethodView):
|
||||
async def get(self):
|
||||
await asyncio.sleep(0)
|
||||
return "GET"
|
||||
|
||||
async def post(self):
|
||||
await asyncio.sleep(0)
|
||||
return "POST"
|
||||
|
||||
|
||||
@pytest.fixture(name="async_app")
|
||||
def _async_app():
|
||||
app = Flask(__name__)
|
||||
|
|
@ -54,11 +73,14 @@ def _async_app():
|
|||
|
||||
app.register_blueprint(blueprint, url_prefix="/bp")
|
||||
|
||||
app.add_url_rule("/view", view_func=AsyncView.as_view("view"))
|
||||
app.add_url_rule("/methodview", view_func=AsyncMethodView.as_view("methodview"))
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7")
|
||||
@pytest.mark.parametrize("path", ["/", "/home", "/bp/"])
|
||||
@pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"])
|
||||
def test_async_route(path, async_app):
|
||||
test_client = async_app.test_client()
|
||||
response = test_client.get(path)
|
||||
|
|
@ -136,5 +158,6 @@ def test_async_before_after_request():
|
|||
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 7), reason="should only raise Python < 3.7")
|
||||
def test_async_runtime_error():
|
||||
app = Flask(__name__)
|
||||
with pytest.raises(RuntimeError):
|
||||
run_async(None)
|
||||
app.async_to_sync(None)
|
||||
|
|
|
|||
|
|
@ -1448,7 +1448,6 @@ def test_static_url_empty_path_default(app):
|
|||
rv.close()
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python >= 3.6")
|
||||
def test_static_folder_with_pathlib_path(app):
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -1631,7 +1630,7 @@ def test_url_processors(app, client):
|
|||
|
||||
|
||||
def test_inject_blueprint_url_defaults(app):
|
||||
bp = flask.Blueprint("foo.bar.baz", __name__, template_folder="template")
|
||||
bp = flask.Blueprint("foo", __name__, template_folder="template")
|
||||
|
||||
@bp.url_defaults
|
||||
def bp_defaults(endpoint, values):
|
||||
|
|
@ -1644,12 +1643,12 @@ def test_inject_blueprint_url_defaults(app):
|
|||
app.register_blueprint(bp)
|
||||
|
||||
values = dict()
|
||||
app.inject_url_defaults("foo.bar.baz.view", values)
|
||||
app.inject_url_defaults("foo.view", values)
|
||||
expected = dict(page="login")
|
||||
assert values == expected
|
||||
|
||||
with app.test_request_context("/somepage"):
|
||||
url = flask.url_for("foo.bar.baz.view")
|
||||
url = flask.url_for("foo.view")
|
||||
expected = "/login"
|
||||
assert url == expected
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import functools
|
||||
|
||||
import pytest
|
||||
from jinja2 import TemplateNotFound
|
||||
from werkzeug.http import parse_cache_control_header
|
||||
|
|
@ -142,7 +140,7 @@ def test_blueprint_url_defaults(app, client):
|
|||
return str(bar)
|
||||
|
||||
app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
|
||||
app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19})
|
||||
app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19})
|
||||
|
||||
assert client.get("/1/foo").data == b"23/42"
|
||||
assert client.get("/2/foo").data == b"19/42"
|
||||
|
|
@ -253,28 +251,9 @@ def test_templates_list(test_apps):
|
|||
assert templates == ["admin/index.html", "frontend/index.html"]
|
||||
|
||||
|
||||
def test_dotted_names(app, client):
|
||||
frontend = flask.Blueprint("myapp.frontend", __name__)
|
||||
backend = flask.Blueprint("myapp.backend", __name__)
|
||||
|
||||
@frontend.route("/fe")
|
||||
def frontend_index():
|
||||
return flask.url_for("myapp.backend.backend_index")
|
||||
|
||||
@frontend.route("/fe2")
|
||||
def frontend_page2():
|
||||
return flask.url_for(".frontend_index")
|
||||
|
||||
@backend.route("/be")
|
||||
def backend_index():
|
||||
return flask.url_for("myapp.frontend.frontend_index")
|
||||
|
||||
app.register_blueprint(frontend)
|
||||
app.register_blueprint(backend)
|
||||
|
||||
assert client.get("/fe").data.strip() == b"/be"
|
||||
assert client.get("/fe2").data.strip() == b"/fe"
|
||||
assert client.get("/be").data.strip() == b"/fe"
|
||||
def test_dotted_name_not_allowed(app, client):
|
||||
with pytest.raises(ValueError):
|
||||
flask.Blueprint("app.ui", __name__)
|
||||
|
||||
|
||||
def test_dotted_names_from_app(app, client):
|
||||
|
|
@ -343,62 +322,19 @@ def test_route_decorator_custom_endpoint(app, client):
|
|||
def test_route_decorator_custom_endpoint_with_dots(app, client):
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
|
||||
@bp.route("/foo")
|
||||
def foo():
|
||||
return flask.request.endpoint
|
||||
with pytest.raises(ValueError):
|
||||
bp.route("/", endpoint="a.b")(lambda: "")
|
||||
|
||||
try:
|
||||
with pytest.raises(ValueError):
|
||||
bp.add_url_rule("/", endpoint="a.b")
|
||||
|
||||
@bp.route("/bar", endpoint="bar.bar")
|
||||
def foo_bar():
|
||||
return flask.request.endpoint
|
||||
def view():
|
||||
return ""
|
||||
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("expected AssertionError not raised")
|
||||
view.__name__ = "a.b"
|
||||
|
||||
try:
|
||||
|
||||
@bp.route("/bar/123", endpoint="bar.123")
|
||||
def foo_bar_foo():
|
||||
return flask.request.endpoint
|
||||
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("expected AssertionError not raised")
|
||||
|
||||
def foo_foo_foo():
|
||||
pass
|
||||
|
||||
pytest.raises(
|
||||
AssertionError,
|
||||
lambda: bp.add_url_rule("/bar/123", endpoint="bar.123", view_func=foo_foo_foo),
|
||||
)
|
||||
|
||||
pytest.raises(
|
||||
AssertionError, bp.route("/bar/123", endpoint="bar.123"), lambda: None
|
||||
)
|
||||
|
||||
foo_foo_foo.__name__ = "bar.123"
|
||||
|
||||
pytest.raises(
|
||||
AssertionError, lambda: bp.add_url_rule("/bar/123", view_func=foo_foo_foo)
|
||||
)
|
||||
|
||||
bp.add_url_rule(
|
||||
"/bar/456", endpoint="foofoofoo", view_func=functools.partial(foo_foo_foo)
|
||||
)
|
||||
|
||||
app.register_blueprint(bp, url_prefix="/py")
|
||||
|
||||
assert client.get("/py/foo").data == b"bp.foo"
|
||||
# The rule's didn't actually made it through
|
||||
rv = client.get("/py/bar")
|
||||
assert rv.status_code == 404
|
||||
rv = client.get("/py/bar/123")
|
||||
assert rv.status_code == 404
|
||||
with pytest.raises(ValueError):
|
||||
bp.add_url_rule("/", view_func=view)
|
||||
|
||||
|
||||
def test_endpoint_decorator(app, client):
|
||||
|
|
@ -899,3 +835,89 @@ def test_nested_blueprint(app, client):
|
|||
assert client.get("/parent/no").data == b"Parent no"
|
||||
assert client.get("/parent/child/no").data == b"Parent no"
|
||||
assert client.get("/parent/child/grandchild/no").data == b"Grandchild no"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"parent_init, child_init, parent_registration, child_registration",
|
||||
[
|
||||
("/parent", "/child", None, None),
|
||||
("/parent", None, None, "/child"),
|
||||
(None, None, "/parent", "/child"),
|
||||
("/other", "/something", "/parent", "/child"),
|
||||
],
|
||||
)
|
||||
def test_nesting_url_prefixes(
|
||||
parent_init,
|
||||
child_init,
|
||||
parent_registration,
|
||||
child_registration,
|
||||
app,
|
||||
client,
|
||||
) -> None:
|
||||
parent = flask.Blueprint("parent", __name__, url_prefix=parent_init)
|
||||
child = flask.Blueprint("child", __name__, url_prefix=child_init)
|
||||
|
||||
@child.route("/")
|
||||
def index():
|
||||
return "index"
|
||||
|
||||
parent.register_blueprint(child, url_prefix=child_registration)
|
||||
app.register_blueprint(parent, url_prefix=parent_registration)
|
||||
|
||||
response = client.get("/parent/child/")
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_unique_blueprint_names(app, client) -> None:
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
bp2 = flask.Blueprint("bp", __name__)
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
with pytest.warns(UserWarning):
|
||||
app.register_blueprint(bp) # same bp, same name, warning
|
||||
|
||||
app.register_blueprint(bp, name="again") # same bp, different name, ok
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
app.register_blueprint(bp2) # different bp, same name, error
|
||||
|
||||
app.register_blueprint(bp2, name="alt") # different bp, different name, ok
|
||||
|
||||
|
||||
def test_self_registration(app, client) -> None:
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
with pytest.raises(ValueError):
|
||||
bp.register_blueprint(bp)
|
||||
|
||||
|
||||
def test_blueprint_renaming(app, client) -> None:
|
||||
bp = flask.Blueprint("bp", __name__)
|
||||
bp2 = flask.Blueprint("bp2", __name__)
|
||||
|
||||
@bp.get("/")
|
||||
def index():
|
||||
return flask.request.endpoint
|
||||
|
||||
@bp.get("/error")
|
||||
def error():
|
||||
flask.abort(403)
|
||||
|
||||
@bp.errorhandler(403)
|
||||
def forbidden(_: Exception):
|
||||
return "Error", 403
|
||||
|
||||
@bp2.get("/")
|
||||
def index2():
|
||||
return flask.request.endpoint
|
||||
|
||||
bp.register_blueprint(bp2, url_prefix="/a", name="sub")
|
||||
app.register_blueprint(bp, url_prefix="/a")
|
||||
app.register_blueprint(bp, url_prefix="/b", name="alt")
|
||||
|
||||
assert client.get("/a/").data == b"bp.index"
|
||||
assert client.get("/b/").data == b"alt.index"
|
||||
assert client.get("/a/a/").data == b"bp.sub.index2"
|
||||
assert client.get("/b/a/").data == b"alt.sub.index2"
|
||||
assert client.get("/a/error").data == b"Error"
|
||||
assert client.get("/b/error").data == b"Error"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import ssl
|
|||
import sys
|
||||
import types
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import click
|
||||
|
|
@ -29,8 +30,8 @@ from flask.cli import run_command
|
|||
from flask.cli import ScriptInfo
|
||||
from flask.cli import with_appcontext
|
||||
|
||||
cwd = os.getcwd()
|
||||
test_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "test_apps"))
|
||||
cwd = Path.cwd()
|
||||
test_path = (Path(__file__) / ".." / "test_apps").resolve()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -152,29 +153,25 @@ def test_find_best_app(test_apps):
|
|||
(
|
||||
("test", cwd, "test"),
|
||||
("test.py", cwd, "test"),
|
||||
("a/test", os.path.join(cwd, "a"), "test"),
|
||||
("a/test", cwd / "a", "test"),
|
||||
("test/__init__.py", cwd, "test"),
|
||||
("test/__init__", cwd, "test"),
|
||||
# nested package
|
||||
(
|
||||
os.path.join(test_path, "cliapp", "inner1", "__init__"),
|
||||
test_path / "cliapp" / "inner1" / "__init__",
|
||||
test_path,
|
||||
"cliapp.inner1",
|
||||
),
|
||||
(
|
||||
os.path.join(test_path, "cliapp", "inner1", "inner2"),
|
||||
test_path / "cliapp" / "inner1" / "inner2",
|
||||
test_path,
|
||||
"cliapp.inner1.inner2",
|
||||
),
|
||||
# dotted name
|
||||
("test.a.b", cwd, "test.a.b"),
|
||||
(os.path.join(test_path, "cliapp.app"), test_path, "cliapp.app"),
|
||||
(test_path / "cliapp.app", test_path, "cliapp.app"),
|
||||
# not a Python file, will be caught during import
|
||||
(
|
||||
os.path.join(test_path, "cliapp", "message.txt"),
|
||||
test_path,
|
||||
"cliapp.message.txt",
|
||||
),
|
||||
(test_path / "cliapp" / "message.txt", test_path, "cliapp.message.txt"),
|
||||
),
|
||||
)
|
||||
def test_prepare_import(request, value, path, result):
|
||||
|
|
@ -193,7 +190,7 @@ def test_prepare_import(request, value, path, result):
|
|||
request.addfinalizer(reset_path)
|
||||
|
||||
assert prepare_import(value) == result
|
||||
assert sys.path[0] == path
|
||||
assert sys.path[0] == str(path)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -278,9 +275,8 @@ def test_scriptinfo(test_apps, monkeypatch):
|
|||
assert obj.load_app() is app
|
||||
|
||||
# import app with module's absolute path
|
||||
cli_app_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "test_apps", "cliapp", "app.py")
|
||||
)
|
||||
cli_app_path = str(test_path / "cliapp" / "app.py")
|
||||
|
||||
obj = ScriptInfo(app_import_path=cli_app_path)
|
||||
app = obj.load_app()
|
||||
assert app.name == "testapp"
|
||||
|
|
@ -302,19 +298,13 @@ def test_scriptinfo(test_apps, monkeypatch):
|
|||
pytest.raises(NoAppException, obj.load_app)
|
||||
|
||||
# import app from wsgi.py in current directory
|
||||
monkeypatch.chdir(
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "test_apps", "helloworld")
|
||||
)
|
||||
)
|
||||
monkeypatch.chdir(test_path / "helloworld")
|
||||
obj = ScriptInfo()
|
||||
app = obj.load_app()
|
||||
assert app.name == "hello"
|
||||
|
||||
# import app from app.py in current directory
|
||||
monkeypatch.chdir(
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), "test_apps", "cliapp"))
|
||||
)
|
||||
monkeypatch.chdir(test_path / "cliapp")
|
||||
obj = ScriptInfo()
|
||||
app = obj.load_app()
|
||||
assert app.name == "testapp"
|
||||
|
|
@ -513,7 +503,7 @@ def test_load_dotenv(monkeypatch):
|
|||
monkeypatch.setenv("EGGS", "3")
|
||||
monkeypatch.chdir(test_path)
|
||||
assert load_dotenv()
|
||||
assert os.getcwd() == test_path
|
||||
assert Path.cwd() == test_path
|
||||
# .flaskenv doesn't overwrite .env
|
||||
assert os.environ["FOO"] == "env"
|
||||
# set only in .flaskenv
|
||||
|
|
@ -533,9 +523,8 @@ def test_dotenv_path(monkeypatch):
|
|||
for item in ("FOO", "BAR", "EGGS"):
|
||||
monkeypatch._setitem.append((os.environ, item, notset))
|
||||
|
||||
cwd = os.getcwd()
|
||||
load_dotenv(os.path.join(test_path, ".flaskenv"))
|
||||
assert os.getcwd() == cwd
|
||||
load_dotenv(test_path / ".flaskenv")
|
||||
assert Path.cwd() == cwd
|
||||
assert "FOO" in os.environ
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from werkzeug.routing import BaseConverter
|
||||
|
||||
from flask import has_request_context
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask import url_for
|
||||
|
||||
|
||||
|
|
@ -28,12 +29,13 @@ def test_custom_converters(app, client):
|
|||
def test_context_available(app, client):
|
||||
class ContextConverter(BaseConverter):
|
||||
def to_python(self, value):
|
||||
assert has_request_context()
|
||||
assert request is not None
|
||||
assert session is not None
|
||||
return value
|
||||
|
||||
app.url_map.converters["ctx"] = ContextConverter
|
||||
|
||||
@app.route("/<ctx:name>")
|
||||
@app.get("/<ctx:name>")
|
||||
def index(name):
|
||||
return name
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,26 @@ import flask
|
|||
from flask.sessions import SessionInterface
|
||||
|
||||
|
||||
def test_open_session_endpoint_not_none():
|
||||
# Define a session interface that breaks if request.endpoint is None
|
||||
def test_open_session_with_endpoint():
|
||||
"""If request.endpoint (or other URL matching behavior) is needed
|
||||
while loading the session, RequestContext.match_request() can be
|
||||
called manually.
|
||||
"""
|
||||
|
||||
class MySessionInterface(SessionInterface):
|
||||
def save_session(self):
|
||||
def save_session(self, app, session, response):
|
||||
pass
|
||||
|
||||
def open_session(self, _, request):
|
||||
def open_session(self, app, request):
|
||||
flask._request_ctx_stack.top.match_request()
|
||||
assert request.endpoint is not None
|
||||
|
||||
def index():
|
||||
return "Hello World!"
|
||||
|
||||
# Confirm a 200 response, indicating that request.endpoint was NOT None
|
||||
app = flask.Flask(__name__)
|
||||
app.route("/")(index)
|
||||
app.session_interface = MySessionInterface()
|
||||
response = app.test_client().open("/")
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return "Hello, World!"
|
||||
|
||||
response = app.test_client().get("/")
|
||||
assert response.status_code == 200
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue