Merge branch 'main' into class-route

This commit is contained in:
Grey Li 2021-08-06 09:47:32 +08:00 committed by GitHub
commit e5fb65b171
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 902 additions and 534 deletions

View file

@ -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)

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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