I just want the tutorial

This commit is contained in:
Jose Palazon 2024-09-06 00:18:20 +01:00
parent 2fec0b206c
commit 127440c727
235 changed files with 46 additions and 33059 deletions

View file

@ -1,160 +1,62 @@
import os
import pkgutil
import sys
import tempfile
import pytest
from _pytest import monkeypatch
from flask import Flask
from flask.globals import request_ctx
from flaskr import create_app
from flaskr.db import get_db
from flaskr.db import init_db
@pytest.fixture(scope="session", autouse=True)
def _standard_os_environ():
"""Set up ``os.environ`` at the start of the test session to have
standard values. Returns a list of operations that is used by
:func:`._reset_os_environ` after each test.
"""
mp = monkeypatch.MonkeyPatch()
out = (
(os.environ, "FLASK_ENV_FILE", monkeypatch.notset),
(os.environ, "FLASK_APP", monkeypatch.notset),
(os.environ, "FLASK_DEBUG", monkeypatch.notset),
(os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset),
(os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset),
)
for _, key, value in out:
if value is monkeypatch.notset:
mp.delenv(key, False)
else:
mp.setenv(key, value)
yield out
mp.undo()
@pytest.fixture(autouse=True)
def _reset_os_environ(monkeypatch, _standard_os_environ):
"""Reset ``os.environ`` to the standard environ after each test,
in case a test changed something without cleaning up.
"""
monkeypatch._setitem.extend(_standard_os_environ)
# read in SQL for populating test data
with open(os.path.join(os.path.dirname(__file__), "data.sql"), "rb") as f:
_data_sql = f.read().decode("utf8")
@pytest.fixture
def app():
app = Flask("flask_test", root_path=os.path.dirname(__file__))
app.config.update(
TESTING=True,
SECRET_KEY="test key",
)
return app
"""Create and configure a new app instance for each test."""
# create a temporary file to isolate the database for each test
db_fd, db_path = tempfile.mkstemp()
# create the app with common test config
app = create_app({"TESTING": True, "DATABASE": db_path})
# create the database and load test data
with app.app_context():
init_db()
get_db().executescript(_data_sql)
@pytest.fixture
def app_ctx(app):
with app.app_context() as ctx:
yield ctx
yield app
@pytest.fixture
def req_ctx(app):
with app.test_request_context() as ctx:
yield ctx
# close and remove the temporary database
os.close(db_fd)
os.unlink(db_path)
@pytest.fixture
def client(app):
"""A test client for the app."""
return app.test_client()
@pytest.fixture
def test_apps(monkeypatch):
monkeypatch.syspath_prepend(os.path.join(os.path.dirname(__file__), "test_apps"))
original_modules = set(sys.modules.keys())
yield
# Remove any imports cached during the test. Otherwise "import app"
# will work in the next test even though it's no longer on the path.
for key in sys.modules.keys() - original_modules:
sys.modules.pop(key)
def runner(app):
"""A test runner for the app's Click commands."""
return app.test_cli_runner()
@pytest.fixture(autouse=True)
def leak_detector():
yield
class AuthActions:
def __init__(self, client):
self._client = client
# make sure we're not leaking a request context since we are
# testing flask internally in debug mode in a few cases
leaks = []
while request_ctx:
leaks.append(request_ctx._get_current_object())
request_ctx.pop()
def login(self, username="test", password="test"):
return self._client.post(
"/auth/login", data={"username": username, "password": password}
)
assert leaks == []
@pytest.fixture(params=(True, False))
def limit_loader(request, monkeypatch):
"""Patch pkgutil.get_loader to give loader without get_filename or archive.
This provides for tests where a system has custom loaders, e.g. Google App
Engine's HardenedModulesHook, which have neither the `get_filename` method
nor the `archive` attribute.
This fixture will run the testcase twice, once with and once without the
limitation/mock.
"""
if not request.param:
return
class LimitedLoader:
def __init__(self, loader):
self.loader = loader
def __getattr__(self, name):
if name in {"archive", "get_filename"}:
raise AttributeError(f"Mocking a loader which does not have {name!r}.")
return getattr(self.loader, name)
old_get_loader = pkgutil.get_loader
def get_loader(*args, **kwargs):
return LimitedLoader(old_get_loader(*args, **kwargs))
monkeypatch.setattr(pkgutil, "get_loader", get_loader)
def logout(self):
return self._client.get("/auth/logout")
@pytest.fixture
def modules_tmp_path(tmp_path, monkeypatch):
"""A temporary directory added to sys.path."""
rv = tmp_path / "modules_tmp"
rv.mkdir()
monkeypatch.syspath_prepend(os.fspath(rv))
return rv
@pytest.fixture
def modules_tmp_path_prefix(modules_tmp_path, monkeypatch):
monkeypatch.setattr(sys, "prefix", os.fspath(modules_tmp_path))
return modules_tmp_path
@pytest.fixture
def site_packages(modules_tmp_path, monkeypatch):
"""Create a fake site-packages."""
py_dir = f"python{sys.version_info.major}.{sys.version_info.minor}"
rv = modules_tmp_path / "lib" / py_dir / "site-packages"
rv.mkdir(parents=True)
monkeypatch.syspath_prepend(os.fspath(rv))
return rv
@pytest.fixture
def purge_module(request):
def inner(name):
request.addfinalizer(lambda: sys.modules.pop(name, None))
return inner
def auth(client):
return AuthActions(client)

8
tests/data.sql Normal file
View file

@ -0,0 +1,8 @@
INSERT INTO user (username, password)
VALUES
('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'),
('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79');
INSERT INTO post (title, body, author_id, created)
VALUES
('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00');

View file

@ -1,4 +0,0 @@
{
"TEST_KEY": "foo",
"SECRET_KEY": "config"
}

View file

@ -1,2 +0,0 @@
TEST_KEY="foo"
SECRET_KEY="config"

View file

@ -1 +0,0 @@
<h1>Hello World!</h1>

View file

@ -1 +0,0 @@
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}

View file

@ -1 +0,0 @@
<p>{{ value }}|{{ injected_value }}

View file

@ -1,6 +0,0 @@
{{ text }}
{{ html }}
{% autoescape false %}{{ text }}
{{ html }}{% endautoescape %}
{% autoescape true %}{{ text }}
{{ html }}{% endautoescape %}

View file

@ -1 +0,0 @@
{{ foo}} Mail

View file

@ -1 +0,0 @@
I'm nested

View file

@ -1,8 +0,0 @@
{{ text }}
{{ html }}
{% autoescape false %}{{ text }}
{{ html }}{% endautoescape %}
{% autoescape true %}{{ text }}
{{ html }}{% endautoescape %}
{{ text }}
{{ html }}

View file

@ -1 +0,0 @@
<h1>{{ whiskey }}</h1>

View file

@ -1 +0,0 @@
{{ value|super_reverse }}

View file

@ -1,3 +0,0 @@
{% if value is boolean %}
Success!
{% endif %}

View file

@ -1,209 +0,0 @@
import pytest
import flask
from flask.globals import app_ctx
from flask.globals import request_ctx
def test_basic_url_generation(app):
app.config["SERVER_NAME"] = "localhost"
app.config["PREFERRED_URL_SCHEME"] = "https"
@app.route("/")
def index():
pass
with app.app_context():
rv = flask.url_for("index")
assert rv == "https://localhost/"
def test_url_generation_requires_server_name(app):
with app.app_context():
with pytest.raises(RuntimeError):
flask.url_for("index")
def test_url_generation_without_context_fails():
with pytest.raises(RuntimeError):
flask.url_for("index")
def test_request_context_means_app_context(app):
with app.test_request_context():
assert flask.current_app._get_current_object() is app
assert not flask.current_app
def test_app_context_provides_current_app(app):
with app.app_context():
assert flask.current_app._get_current_object() is app
assert not flask.current_app
def test_app_tearing_down(app):
cleanup_stuff = []
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
with app.app_context():
pass
assert cleanup_stuff == [None]
def test_app_tearing_down_with_previous_exception(app):
cleanup_stuff = []
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
try:
raise Exception("dummy")
except Exception:
pass
with app.app_context():
pass
assert cleanup_stuff == [None]
def test_app_tearing_down_with_handled_exception_by_except_block(app):
cleanup_stuff = []
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
with app.app_context():
try:
raise Exception("dummy")
except Exception:
pass
assert cleanup_stuff == [None]
def test_app_tearing_down_with_handled_exception_by_app_handler(app, client):
app.config["PROPAGATE_EXCEPTIONS"] = True
cleanup_stuff = []
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
@app.route("/")
def index():
raise Exception("dummy")
@app.errorhandler(Exception)
def handler(f):
return flask.jsonify(str(f))
with app.app_context():
client.get("/")
assert cleanup_stuff == [None]
def test_app_tearing_down_with_unhandled_exception(app, client):
app.config["PROPAGATE_EXCEPTIONS"] = True
cleanup_stuff = []
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
@app.route("/")
def index():
raise ValueError("dummy")
with pytest.raises(ValueError, match="dummy"):
with app.app_context():
client.get("/")
assert len(cleanup_stuff) == 1
assert isinstance(cleanup_stuff[0], ValueError)
assert str(cleanup_stuff[0]) == "dummy"
def test_app_ctx_globals_methods(app, app_ctx):
# get
assert flask.g.get("foo") is None
assert flask.g.get("foo", "bar") == "bar"
# __contains__
assert "foo" not in flask.g
flask.g.foo = "bar"
assert "foo" in flask.g
# setdefault
flask.g.setdefault("bar", "the cake is a lie")
flask.g.setdefault("bar", "hello world")
assert flask.g.bar == "the cake is a lie"
# pop
assert flask.g.pop("bar") == "the cake is a lie"
with pytest.raises(KeyError):
flask.g.pop("bar")
assert flask.g.pop("bar", "more cake") == "more cake"
# __iter__
assert list(flask.g) == ["foo"]
# __repr__
assert repr(flask.g) == "<flask.g of 'flask_test'>"
def test_custom_app_ctx_globals_class(app):
class CustomRequestGlobals:
def __init__(self):
self.spam = "eggs"
app.app_ctx_globals_class = CustomRequestGlobals
with app.app_context():
assert flask.render_template_string("{{ g.spam }}") == "eggs"
def test_context_refcounts(app, client):
called = []
@app.teardown_request
def teardown_req(error=None):
called.append("request")
@app.teardown_appcontext
def teardown_app(error=None):
called.append("app")
@app.route("/")
def index():
with app_ctx:
with request_ctx:
pass
assert flask.request.environ["werkzeug.request"] is not None
return ""
res = client.get("/")
assert res.status_code == 200
assert res.data == b""
assert called == ["request", "app"]
def test_clean_pop(app):
app.testing = False
called = []
@app.teardown_request
def teardown_req(error=None):
raise ZeroDivisionError
@app.teardown_appcontext
def teardown_app(error=None):
called.append("TEARDOWN")
with app.app_context():
called.append(flask.current_app.name)
assert called == ["flask_test", "TEARDOWN"]
assert not flask.current_app

View file

@ -1,4 +0,0 @@
FOO=env
SPAM=1
EGGS=2
HAM=火腿

View file

@ -1,3 +0,0 @@
FOO=flaskenv
BAR=bar
EGGS=0

View file

@ -1,9 +0,0 @@
from flask import Flask
app = Flask(__name__)
app.config["DEBUG"] = True
from blueprintapp.apps.admin import admin # noqa: E402
from blueprintapp.apps.frontend import frontend # noqa: E402
app.register_blueprint(admin)
app.register_blueprint(frontend)

View file

@ -1,20 +0,0 @@
from flask import Blueprint
from flask import render_template
admin = Blueprint(
"admin",
__name__,
url_prefix="/admin",
template_folder="templates",
static_folder="static",
)
@admin.route("/")
def index():
return render_template("admin/index.html")
@admin.route("/index2")
def index2():
return render_template("./admin/index.html")

View file

@ -1 +0,0 @@
/* nested file */

View file

@ -1 +0,0 @@
Admin File

View file

@ -1 +0,0 @@
Hello from the Admin

View file

@ -1,14 +0,0 @@
from flask import Blueprint
from flask import render_template
frontend = Blueprint("frontend", __name__, template_folder="templates")
@frontend.route("/")
def index():
return render_template("frontend/index.html")
@frontend.route("/missing")
def missing_template():
return render_template("missing_template.html")

View file

@ -1 +0,0 @@
Hello from the Frontend

View file

@ -1,3 +0,0 @@
from flask import Flask
testapp = Flask("testapp")

View file

@ -1,13 +0,0 @@
from flask import Flask
def create_app():
return Flask("app")
def create_app2(foo, bar):
return Flask("_".join(["app2", foo, bar]))
def no_app():
pass

View file

@ -1,5 +0,0 @@
from flask import Flask
raise ImportError()
testapp = Flask("testapp")

View file

@ -1,3 +0,0 @@
from flask import Flask
application = Flask(__name__)

View file

@ -1,3 +0,0 @@
from flask import Flask
app = Flask(__name__)

View file

@ -1 +0,0 @@
So long, and thanks for all the fish.

View file

@ -1,4 +0,0 @@
from flask import Flask
app1 = Flask("app1")
app2 = Flask("app2")

View file

@ -1,8 +0,0 @@
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"

View file

@ -1 +0,0 @@
from hello import app # noqa: F401

View file

@ -1,3 +0,0 @@
from flask import Module
mod = Module(__name__, "foo", subdomain="foo")

View file

@ -1 +0,0 @@
Hello Subdomain

View file

@ -1,145 +0,0 @@
import asyncio
import pytest
from flask import Blueprint
from flask import Flask
from flask import request
from flask.views import MethodView
from flask.views import View
pytest.importorskip("asgiref")
class AppError(Exception):
pass
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__)
@app.route("/", methods=["GET", "POST"])
@app.route("/home", methods=["GET", "POST"])
async def index():
await asyncio.sleep(0)
return request.method
@app.errorhandler(AppError)
async def handle(_):
return "", 412
@app.route("/error")
async def error():
raise AppError()
blueprint = Blueprint("bp", __name__)
@blueprint.route("/", methods=["GET", "POST"])
async def bp_index():
await asyncio.sleep(0)
return request.method
@blueprint.errorhandler(BlueprintError)
async def bp_handle(_):
return "", 412
@blueprint.route("/error")
async def bp_error():
raise BlueprintError()
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.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"])
def test_async_route(path, async_app):
test_client = async_app.test_client()
response = test_client.get(path)
assert b"GET" in response.get_data()
response = test_client.post(path)
assert b"POST" in response.get_data()
@pytest.mark.parametrize("path", ["/error", "/bp/error"])
def test_async_error_handler(path, async_app):
test_client = async_app.test_client()
response = test_client.get(path)
assert response.status_code == 412
def test_async_before_after_request():
app_before_called = False
app_after_called = False
bp_before_called = False
bp_after_called = False
app = Flask(__name__)
@app.route("/")
def index():
return ""
@app.before_request
async def before():
nonlocal app_before_called
app_before_called = True
@app.after_request
async def after(response):
nonlocal app_after_called
app_after_called = True
return response
blueprint = Blueprint("bp", __name__)
@blueprint.route("/")
def bp_index():
return ""
@blueprint.before_request
async def bp_before():
nonlocal bp_before_called
bp_before_called = True
@blueprint.after_request
async def bp_after(response):
nonlocal bp_after_called
bp_after_called = True
return response
app.register_blueprint(blueprint, url_prefix="/bp")
test_client = app.test_client()
test_client.get("/")
assert app_before_called
assert app_after_called
test_client.get("/bp/")
assert bp_before_called
assert bp_after_called

69
tests/test_auth.py Normal file
View file

@ -0,0 +1,69 @@
import pytest
from flask import g
from flask import session
from flaskr.db import get_db
def test_register(client, app):
# test that viewing the page renders without template errors
assert client.get("/auth/register").status_code == 200
# test that successful registration redirects to the login page
response = client.post("/auth/register", data={"username": "a", "password": "a"})
assert response.headers["Location"] == "/auth/login"
# test that the user was inserted into the database
with app.app_context():
assert (
get_db().execute("SELECT * FROM user WHERE username = 'a'").fetchone()
is not None
)
@pytest.mark.parametrize(
("username", "password", "message"),
(
("", "", b"Username is required."),
("a", "", b"Password is required."),
("test", "test", b"already registered"),
),
)
def test_register_validate_input(client, username, password, message):
response = client.post(
"/auth/register", data={"username": username, "password": password}
)
assert message in response.data
def test_login(client, auth):
# test that viewing the page renders without template errors
assert client.get("/auth/login").status_code == 200
# test that successful login redirects to the index page
response = auth.login()
assert response.headers["Location"] == "/"
# login request set the user_id in the session
# check that the user is loaded from the session
with client:
client.get("/")
assert session["user_id"] == 1
assert g.user["username"] == "test"
@pytest.mark.parametrize(
("username", "password", "message"),
(("a", "test", b"Incorrect username."), ("test", "a", b"Incorrect password.")),
)
def test_login_validate_input(auth, username, password, message):
response = auth.login(username, password)
assert message in response.data
def test_logout(client, auth):
auth.login()
with client:
auth.logout()
assert "user_id" not in session

File diff suppressed because it is too large Load diff

83
tests/test_blog.py Normal file
View file

@ -0,0 +1,83 @@
import pytest
from flaskr.db import get_db
def test_index(client, auth):
response = client.get("/")
assert b"Log In" in response.data
assert b"Register" in response.data
auth.login()
response = client.get("/")
assert b"test title" in response.data
assert b"by test on 2018-01-01" in response.data
assert b"test\nbody" in response.data
assert b'href="/1/update"' in response.data
@pytest.mark.parametrize("path", ("/create", "/1/update", "/1/delete"))
def test_login_required(client, path):
response = client.post(path)
assert response.headers["Location"] == "/auth/login"
def test_author_required(app, client, auth):
# change the post author to another user
with app.app_context():
db = get_db()
db.execute("UPDATE post SET author_id = 2 WHERE id = 1")
db.commit()
auth.login()
# current user can't modify other user's post
assert client.post("/1/update").status_code == 403
assert client.post("/1/delete").status_code == 403
# current user doesn't see edit link
assert b'href="/1/update"' not in client.get("/").data
@pytest.mark.parametrize("path", ("/2/update", "/2/delete"))
def test_exists_required(client, auth, path):
auth.login()
assert client.post(path).status_code == 404
def test_create(client, auth, app):
auth.login()
assert client.get("/create").status_code == 200
client.post("/create", data={"title": "created", "body": ""})
with app.app_context():
db = get_db()
count = db.execute("SELECT COUNT(id) FROM post").fetchone()[0]
assert count == 2
def test_update(client, auth, app):
auth.login()
assert client.get("/1/update").status_code == 200
client.post("/1/update", data={"title": "updated", "body": ""})
with app.app_context():
db = get_db()
post = db.execute("SELECT * FROM post WHERE id = 1").fetchone()
assert post["title"] == "updated"
@pytest.mark.parametrize("path", ("/create", "/1/update"))
def test_create_update_validate(client, auth, path):
auth.login()
response = client.post(path, data={"title": "", "body": ""})
assert b"Title is required." in response.data
def test_delete(client, auth, app):
auth.login()
response = client.post("/1/delete")
assert response.headers["Location"] == "/"
with app.app_context():
db = get_db()
post = db.execute("SELECT * FROM post WHERE id = 1").fetchone()
assert post is None

File diff suppressed because it is too large Load diff

View file

@ -1,686 +0,0 @@
# This file was part of Flask-CLI and was modified under the terms of
# its Revised BSD License. Copyright © 2015 CERN.
import importlib.metadata
import os
import platform
import ssl
import sys
import types
from functools import partial
from pathlib import Path
import click
import pytest
from _pytest.monkeypatch import notset
from click.testing import CliRunner
from flask import Blueprint
from flask import current_app
from flask import Flask
from flask.cli import AppGroup
from flask.cli import find_best_app
from flask.cli import FlaskGroup
from flask.cli import get_version
from flask.cli import load_dotenv
from flask.cli import locate_app
from flask.cli import NoAppException
from flask.cli import prepare_import
from flask.cli import run_command
from flask.cli import ScriptInfo
from flask.cli import with_appcontext
cwd = Path.cwd()
test_path = (Path(__file__) / ".." / "test_apps").resolve()
@pytest.fixture
def runner():
return CliRunner()
def test_cli_name(test_apps):
"""Make sure the CLI object's name is the app's name and not the app itself"""
from cliapp.app import testapp
assert testapp.cli.name == testapp.name
def test_find_best_app(test_apps):
class Module:
app = Flask("appname")
assert find_best_app(Module) == Module.app
class Module:
application = Flask("appname")
assert find_best_app(Module) == Module.application
class Module:
myapp = Flask("appname")
assert find_best_app(Module) == Module.myapp
class Module:
@staticmethod
def create_app():
return Flask("appname")
app = find_best_app(Module)
assert isinstance(app, Flask)
assert app.name == "appname"
class Module:
@staticmethod
def create_app(**kwargs):
return Flask("appname")
app = find_best_app(Module)
assert isinstance(app, Flask)
assert app.name == "appname"
class Module:
@staticmethod
def make_app():
return Flask("appname")
app = find_best_app(Module)
assert isinstance(app, Flask)
assert app.name == "appname"
class Module:
myapp = Flask("appname1")
@staticmethod
def create_app():
return Flask("appname2")
assert find_best_app(Module) == Module.myapp
class Module:
myapp = Flask("appname1")
@staticmethod
def create_app():
return Flask("appname2")
assert find_best_app(Module) == Module.myapp
class Module:
pass
pytest.raises(NoAppException, find_best_app, Module)
class Module:
myapp1 = Flask("appname1")
myapp2 = Flask("appname2")
pytest.raises(NoAppException, find_best_app, Module)
class Module:
@staticmethod
def create_app(foo, bar):
return Flask("appname2")
pytest.raises(NoAppException, find_best_app, Module)
class Module:
@staticmethod
def create_app():
raise TypeError("bad bad factory!")
pytest.raises(TypeError, find_best_app, Module)
@pytest.mark.parametrize(
"value,path,result",
(
("test", cwd, "test"),
("test.py", cwd, "test"),
("a/test", cwd / "a", "test"),
("test/__init__.py", cwd, "test"),
("test/__init__", cwd, "test"),
# nested package
(
test_path / "cliapp" / "inner1" / "__init__",
test_path,
"cliapp.inner1",
),
(
test_path / "cliapp" / "inner1" / "inner2",
test_path,
"cliapp.inner1.inner2",
),
# dotted name
("test.a.b", cwd, "test.a.b"),
(test_path / "cliapp.app", test_path, "cliapp.app"),
# not a Python file, will be caught during import
(test_path / "cliapp" / "message.txt", test_path, "cliapp.message.txt"),
),
)
def test_prepare_import(request, value, path, result):
"""Expect the correct path to be set and the correct import and app names
to be returned.
:func:`prepare_exec_for_file` has a side effect where the parent directory
of the given import is added to :data:`sys.path`. This is reset after the
test runs.
"""
original_path = sys.path[:]
def reset_path():
sys.path[:] = original_path
request.addfinalizer(reset_path)
assert prepare_import(value) == result
assert sys.path[0] == str(path)
@pytest.mark.parametrize(
"iname,aname,result",
(
("cliapp.app", None, "testapp"),
("cliapp.app", "testapp", "testapp"),
("cliapp.factory", None, "app"),
("cliapp.factory", "create_app", "app"),
("cliapp.factory", "create_app()", "app"),
("cliapp.factory", 'create_app2("foo", "bar")', "app2_foo_bar"),
# trailing comma space
("cliapp.factory", 'create_app2("foo", "bar", )', "app2_foo_bar"),
# strip whitespace
("cliapp.factory", " create_app () ", "app"),
),
)
def test_locate_app(test_apps, iname, aname, result):
assert locate_app(iname, aname).name == result
@pytest.mark.parametrize(
"iname,aname",
(
("notanapp.py", None),
("cliapp/app", None),
("cliapp.app", "notanapp"),
# not enough arguments
("cliapp.factory", 'create_app2("foo")'),
# invalid identifier
("cliapp.factory", "create_app("),
# no app returned
("cliapp.factory", "no_app"),
# nested import error
("cliapp.importerrorapp", None),
# not a Python file
("cliapp.message.txt", None),
),
)
def test_locate_app_raises(test_apps, iname, aname):
with pytest.raises(NoAppException):
locate_app(iname, aname)
def test_locate_app_suppress_raise(test_apps):
app = locate_app("notanapp.py", None, raise_if_not_found=False)
assert app is None
# only direct import error is suppressed
with pytest.raises(NoAppException):
locate_app("cliapp.importerrorapp", None, raise_if_not_found=False)
def test_get_version(test_apps, capsys):
class MockCtx:
resilient_parsing = False
color = None
def exit(self):
return
ctx = MockCtx()
get_version(ctx, None, "test")
out, err = capsys.readouterr()
assert f"Python {platform.python_version()}" in out
assert f"Flask {importlib.metadata.version('flask')}" in out
assert f"Werkzeug {importlib.metadata.version('werkzeug')}" in out
def test_scriptinfo(test_apps, monkeypatch):
obj = ScriptInfo(app_import_path="cliapp.app:testapp")
app = obj.load_app()
assert app.name == "testapp"
assert obj.load_app() is app
# import app with module's absolute path
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"
assert obj.load_app() is app
obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp")
app = obj.load_app()
assert app.name == "testapp"
assert obj.load_app() is app
def create_app():
return Flask("createapp")
obj = ScriptInfo(create_app=create_app)
app = obj.load_app()
assert app.name == "createapp"
assert obj.load_app() is app
obj = ScriptInfo()
pytest.raises(NoAppException, obj.load_app)
# import app from wsgi.py in current directory
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(test_path / "cliapp")
obj = ScriptInfo()
app = obj.load_app()
assert app.name == "testapp"
def test_app_cli_has_app_context(app, runner):
def _param_cb(ctx, param, value):
# current_app should be available in parameter callbacks
return bool(current_app)
@app.cli.command()
@click.argument("value", callback=_param_cb)
def check(value):
app = click.get_current_context().obj.load_app()
# the loaded app should be the same as current_app
same_app = current_app._get_current_object() is app
return same_app, value
cli = FlaskGroup(create_app=lambda: app)
result = runner.invoke(cli, ["check", "x"], standalone_mode=False)
assert result.return_value == (True, True)
def test_with_appcontext(runner):
@click.command()
@with_appcontext
def testcmd():
click.echo(current_app.name)
obj = ScriptInfo(create_app=lambda: Flask("testapp"))
result = runner.invoke(testcmd, obj=obj)
assert result.exit_code == 0
assert result.output == "testapp\n"
def test_appgroup_app_context(runner):
@click.group(cls=AppGroup)
def cli():
pass
@cli.command()
def test():
click.echo(current_app.name)
@cli.group()
def subgroup():
pass
@subgroup.command()
def test2():
click.echo(current_app.name)
obj = ScriptInfo(create_app=lambda: Flask("testappgroup"))
result = runner.invoke(cli, ["test"], obj=obj)
assert result.exit_code == 0
assert result.output == "testappgroup\n"
result = runner.invoke(cli, ["subgroup", "test2"], obj=obj)
assert result.exit_code == 0
assert result.output == "testappgroup\n"
def test_flaskgroup_app_context(runner):
def create_app():
return Flask("flaskgroup")
@click.group(cls=FlaskGroup, create_app=create_app)
def cli(**params):
pass
@cli.command()
def test():
click.echo(current_app.name)
result = runner.invoke(cli, ["test"])
assert result.exit_code == 0
assert result.output == "flaskgroup\n"
@pytest.mark.parametrize("set_debug_flag", (True, False))
def test_flaskgroup_debug(runner, set_debug_flag):
def create_app():
app = Flask("flaskgroup")
app.debug = True
return app
@click.group(cls=FlaskGroup, create_app=create_app, set_debug_flag=set_debug_flag)
def cli(**params):
pass
@cli.command()
def test():
click.echo(str(current_app.debug))
result = runner.invoke(cli, ["test"])
assert result.exit_code == 0
assert result.output == f"{not set_debug_flag}\n"
def test_flaskgroup_nested(app, runner):
cli = click.Group("cli")
flask_group = FlaskGroup(name="flask", create_app=lambda: app)
cli.add_command(flask_group)
@flask_group.command()
def show():
click.echo(current_app.name)
result = runner.invoke(cli, ["flask", "show"])
assert result.output == "flask_test\n"
def test_no_command_echo_loading_error():
from flask.cli import cli
runner = CliRunner(mix_stderr=False)
result = runner.invoke(cli, ["missing"])
assert result.exit_code == 2
assert "FLASK_APP" in result.stderr
assert "Usage:" in result.stderr
def test_help_echo_loading_error():
from flask.cli import cli
runner = CliRunner(mix_stderr=False)
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
assert "FLASK_APP" in result.stderr
assert "Usage:" in result.stdout
def test_help_echo_exception():
def create_app():
raise Exception("oh no")
cli = FlaskGroup(create_app=create_app)
runner = CliRunner(mix_stderr=False)
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
assert "Exception: oh no" in result.stderr
assert "Usage:" in result.stdout
class TestRoutes:
@pytest.fixture
def app(self):
app = Flask(__name__)
app.add_url_rule(
"/get_post/<int:x>/<int:y>",
methods=["GET", "POST"],
endpoint="yyy_get_post",
)
app.add_url_rule("/zzz_post", methods=["POST"], endpoint="aaa_post")
return app
@pytest.fixture
def invoke(self, app, runner):
cli = FlaskGroup(create_app=lambda: app)
return partial(runner.invoke, cli)
def expect_order(self, order, output):
# skip the header and match the start of each row
for expect, line in zip(order, output.splitlines()[2:]):
# do this instead of startswith for nicer pytest output
assert line[: len(expect)] == expect
def test_simple(self, invoke):
result = invoke(["routes"])
assert result.exit_code == 0
self.expect_order(["aaa_post", "static", "yyy_get_post"], result.output)
def test_sort(self, app, invoke):
default_output = invoke(["routes"]).output
endpoint_output = invoke(["routes", "-s", "endpoint"]).output
assert default_output == endpoint_output
self.expect_order(
["static", "yyy_get_post", "aaa_post"],
invoke(["routes", "-s", "methods"]).output,
)
self.expect_order(
["yyy_get_post", "static", "aaa_post"],
invoke(["routes", "-s", "rule"]).output,
)
match_order = [r.endpoint for r in app.url_map.iter_rules()]
self.expect_order(match_order, invoke(["routes", "-s", "match"]).output)
def test_all_methods(self, invoke):
output = invoke(["routes"]).output
assert "GET, HEAD, OPTIONS, POST" not in output
output = invoke(["routes", "--all-methods"]).output
assert "GET, HEAD, OPTIONS, POST" in output
def test_no_routes(self, runner):
app = Flask(__name__, static_folder=None)
cli = FlaskGroup(create_app=lambda: app)
result = runner.invoke(cli, ["routes"])
assert result.exit_code == 0
assert "No routes were registered." in result.output
def test_subdomain(self, runner):
app = Flask(__name__, static_folder=None)
app.add_url_rule("/a", subdomain="a", endpoint="a")
app.add_url_rule("/b", subdomain="b", endpoint="b")
cli = FlaskGroup(create_app=lambda: app)
result = runner.invoke(cli, ["routes"])
assert result.exit_code == 0
assert "Subdomain" in result.output
def test_host(self, runner):
app = Flask(__name__, static_folder=None, host_matching=True)
app.add_url_rule("/a", host="a", endpoint="a")
app.add_url_rule("/b", host="b", endpoint="b")
cli = FlaskGroup(create_app=lambda: app)
result = runner.invoke(cli, ["routes"])
assert result.exit_code == 0
assert "Host" in result.output
def dotenv_not_available():
try:
import dotenv # noqa: F401
except ImportError:
return True
return False
need_dotenv = pytest.mark.skipif(
dotenv_not_available(), reason="dotenv is not installed"
)
@need_dotenv
def test_load_dotenv(monkeypatch):
# can't use monkeypatch.delitem since the keys don't exist yet
for item in ("FOO", "BAR", "SPAM", "HAM"):
monkeypatch._setitem.append((os.environ, item, notset))
monkeypatch.setenv("EGGS", "3")
monkeypatch.chdir(test_path)
assert load_dotenv()
assert Path.cwd() == test_path
# .flaskenv doesn't overwrite .env
assert os.environ["FOO"] == "env"
# set only in .flaskenv
assert os.environ["BAR"] == "bar"
# set only in .env
assert os.environ["SPAM"] == "1"
# set manually, files don't overwrite
assert os.environ["EGGS"] == "3"
# test env file encoding
assert os.environ["HAM"] == "火腿"
# Non existent file should not load
assert not load_dotenv("non-existent-file")
@need_dotenv
def test_dotenv_path(monkeypatch):
for item in ("FOO", "BAR", "EGGS"):
monkeypatch._setitem.append((os.environ, item, notset))
load_dotenv(test_path / ".flaskenv")
assert Path.cwd() == cwd
assert "FOO" in os.environ
def test_dotenv_optional(monkeypatch):
monkeypatch.setitem(sys.modules, "dotenv", None)
monkeypatch.chdir(test_path)
load_dotenv()
assert "FOO" not in os.environ
@need_dotenv
def test_disable_dotenv_from_env(monkeypatch, runner):
monkeypatch.chdir(test_path)
monkeypatch.setitem(os.environ, "FLASK_SKIP_DOTENV", "1")
runner.invoke(FlaskGroup())
assert "FOO" not in os.environ
def test_run_cert_path():
# no key
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", __file__])
# no cert
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--key", __file__])
# cert specified first
ctx = run_command.make_context("run", ["--cert", __file__, "--key", __file__])
assert ctx.params["cert"] == (__file__, __file__)
# key specified first
ctx = run_command.make_context("run", ["--key", __file__, "--cert", __file__])
assert ctx.params["cert"] == (__file__, __file__)
def test_run_cert_adhoc(monkeypatch):
monkeypatch.setitem(sys.modules, "cryptography", None)
# cryptography not installed
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "adhoc"])
# cryptography installed
monkeypatch.setitem(sys.modules, "cryptography", types.ModuleType("cryptography"))
ctx = run_command.make_context("run", ["--cert", "adhoc"])
assert ctx.params["cert"] == "adhoc"
# no key with adhoc
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "adhoc", "--key", __file__])
def test_run_cert_import(monkeypatch):
monkeypatch.setitem(sys.modules, "not_here", None)
# ImportError
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "not_here"])
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "flask"])
# SSLContext
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
monkeypatch.setitem(sys.modules, "ssl_context", ssl_context)
ctx = run_command.make_context("run", ["--cert", "ssl_context"])
assert ctx.params["cert"] is ssl_context
# no --key with SSLContext
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "ssl_context", "--key", __file__])
def test_run_cert_no_ssl(monkeypatch):
monkeypatch.setitem(sys.modules, "ssl", None)
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "not_here"])
def test_cli_blueprints(app):
"""Test blueprint commands register correctly to the application"""
custom = Blueprint("custom", __name__, cli_group="customized")
nested = Blueprint("nested", __name__)
merged = Blueprint("merged", __name__, cli_group=None)
late = Blueprint("late", __name__)
@custom.cli.command("custom")
def custom_command():
click.echo("custom_result")
@nested.cli.command("nested")
def nested_command():
click.echo("nested_result")
@merged.cli.command("merged")
def merged_command():
click.echo("merged_result")
@late.cli.command("late")
def late_command():
click.echo("late_result")
app.register_blueprint(custom)
app.register_blueprint(nested)
app.register_blueprint(merged)
app.register_blueprint(late, cli_group="late_registration")
app_runner = app.test_cli_runner()
result = app_runner.invoke(args=["customized", "custom"])
assert "custom_result" in result.output
result = app_runner.invoke(args=["nested", "nested"])
assert "nested_result" in result.output
result = app_runner.invoke(args=["merged"])
assert "merged_result" in result.output
result = app_runner.invoke(args=["late_registration", "late"])
assert "late_result" in result.output
def test_cli_empty(app):
"""If a Blueprint's CLI group is empty, do not register it."""
bp = Blueprint("blue", __name__, cli_group="blue")
app.register_blueprint(bp)
result = app.test_cli_runner().invoke(args=["blue", "--help"])
assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}"
def test_run_exclude_patterns():
ctx = run_command.make_context("run", ["--exclude-patterns", __file__])
assert ctx.params["exclude_patterns"] == [__file__]

View file

@ -1,250 +0,0 @@
import json
import os
import pytest
import flask
# config keys used for the TestConfig
TEST_KEY = "foo"
SECRET_KEY = "config"
def common_object_test(app):
assert app.secret_key == "config"
assert app.config["TEST_KEY"] == "foo"
assert "TestConfig" not in app.config
def test_config_from_pyfile():
app = flask.Flask(__name__)
app.config.from_pyfile(f"{__file__.rsplit('.', 1)[0]}.py")
common_object_test(app)
def test_config_from_object():
app = flask.Flask(__name__)
app.config.from_object(__name__)
common_object_test(app)
def test_config_from_file_json():
app = flask.Flask(__name__)
current_dir = os.path.dirname(os.path.abspath(__file__))
app.config.from_file(os.path.join(current_dir, "static", "config.json"), json.load)
common_object_test(app)
def test_config_from_file_toml():
tomllib = pytest.importorskip("tomllib", reason="tomllib added in 3.11")
app = flask.Flask(__name__)
current_dir = os.path.dirname(os.path.abspath(__file__))
app.config.from_file(
os.path.join(current_dir, "static", "config.toml"), tomllib.load, text=False
)
common_object_test(app)
def test_from_prefixed_env(monkeypatch):
monkeypatch.setenv("FLASK_STRING", "value")
monkeypatch.setenv("FLASK_BOOL", "true")
monkeypatch.setenv("FLASK_INT", "1")
monkeypatch.setenv("FLASK_FLOAT", "1.2")
monkeypatch.setenv("FLASK_LIST", "[1, 2]")
monkeypatch.setenv("FLASK_DICT", '{"k": "v"}')
monkeypatch.setenv("NOT_FLASK_OTHER", "other")
app = flask.Flask(__name__)
app.config.from_prefixed_env()
assert app.config["STRING"] == "value"
assert app.config["BOOL"] is True
assert app.config["INT"] == 1
assert app.config["FLOAT"] == 1.2
assert app.config["LIST"] == [1, 2]
assert app.config["DICT"] == {"k": "v"}
assert "OTHER" not in app.config
def test_from_prefixed_env_custom_prefix(monkeypatch):
monkeypatch.setenv("FLASK_A", "a")
monkeypatch.setenv("NOT_FLASK_A", "b")
app = flask.Flask(__name__)
app.config.from_prefixed_env("NOT_FLASK")
assert app.config["A"] == "b"
def test_from_prefixed_env_nested(monkeypatch):
monkeypatch.setenv("FLASK_EXIST__ok", "other")
monkeypatch.setenv("FLASK_EXIST__inner__ik", "2")
monkeypatch.setenv("FLASK_EXIST__new__more", '{"k": false}')
monkeypatch.setenv("FLASK_NEW__K", "v")
app = flask.Flask(__name__)
app.config["EXIST"] = {"ok": "value", "flag": True, "inner": {"ik": 1}}
app.config.from_prefixed_env()
if os.name != "nt":
assert app.config["EXIST"] == {
"ok": "other",
"flag": True,
"inner": {"ik": 2},
"new": {"more": {"k": False}},
}
else:
# Windows env var keys are always uppercase.
assert app.config["EXIST"] == {
"ok": "value",
"OK": "other",
"flag": True,
"inner": {"ik": 1},
"INNER": {"IK": 2},
"NEW": {"MORE": {"k": False}},
}
assert app.config["NEW"] == {"K": "v"}
def test_config_from_mapping():
app = flask.Flask(__name__)
app.config.from_mapping({"SECRET_KEY": "config", "TEST_KEY": "foo"})
common_object_test(app)
app = flask.Flask(__name__)
app.config.from_mapping([("SECRET_KEY", "config"), ("TEST_KEY", "foo")])
common_object_test(app)
app = flask.Flask(__name__)
app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo")
common_object_test(app)
app = flask.Flask(__name__)
app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo", skip_key="skip")
common_object_test(app)
app = flask.Flask(__name__)
with pytest.raises(TypeError):
app.config.from_mapping({}, {})
def test_config_from_class():
class Base:
TEST_KEY = "foo"
class Test(Base):
SECRET_KEY = "config"
app = flask.Flask(__name__)
app.config.from_object(Test)
common_object_test(app)
def test_config_from_envvar(monkeypatch):
monkeypatch.setattr("os.environ", {})
app = flask.Flask(__name__)
with pytest.raises(RuntimeError) as e:
app.config.from_envvar("FOO_SETTINGS")
assert "'FOO_SETTINGS' is not set" in str(e.value)
assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
monkeypatch.setattr(
"os.environ", {"FOO_SETTINGS": f"{__file__.rsplit('.', 1)[0]}.py"}
)
assert app.config.from_envvar("FOO_SETTINGS")
common_object_test(app)
def test_config_from_envvar_missing(monkeypatch):
monkeypatch.setattr("os.environ", {"FOO_SETTINGS": "missing.cfg"})
app = flask.Flask(__name__)
with pytest.raises(IOError) as e:
app.config.from_envvar("FOO_SETTINGS")
msg = str(e.value)
assert msg.startswith(
"[Errno 2] Unable to load configuration file (No such file or directory):"
)
assert msg.endswith("missing.cfg'")
assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
def test_config_missing():
app = flask.Flask(__name__)
with pytest.raises(IOError) as e:
app.config.from_pyfile("missing.cfg")
msg = str(e.value)
assert msg.startswith(
"[Errno 2] Unable to load configuration file (No such file or directory):"
)
assert msg.endswith("missing.cfg'")
assert not app.config.from_pyfile("missing.cfg", silent=True)
def test_config_missing_file():
app = flask.Flask(__name__)
with pytest.raises(IOError) as e:
app.config.from_file("missing.json", load=json.load)
msg = str(e.value)
assert msg.startswith(
"[Errno 2] Unable to load configuration file (No such file or directory):"
)
assert msg.endswith("missing.json'")
assert not app.config.from_file("missing.json", load=json.load, silent=True)
def test_custom_config_class():
class Config(flask.Config):
pass
class Flask(flask.Flask):
config_class = Config
app = Flask(__name__)
assert isinstance(app.config, Config)
app.config.from_object(__name__)
common_object_test(app)
def test_session_lifetime():
app = flask.Flask(__name__)
app.config["PERMANENT_SESSION_LIFETIME"] = 42
assert app.permanent_session_lifetime.seconds == 42
def test_get_namespace():
app = flask.Flask(__name__)
app.config["FOO_OPTION_1"] = "foo option 1"
app.config["FOO_OPTION_2"] = "foo option 2"
app.config["BAR_STUFF_1"] = "bar stuff 1"
app.config["BAR_STUFF_2"] = "bar stuff 2"
foo_options = app.config.get_namespace("FOO_")
assert 2 == len(foo_options)
assert "foo option 1" == foo_options["option_1"]
assert "foo option 2" == foo_options["option_2"]
bar_options = app.config.get_namespace("BAR_", lowercase=False)
assert 2 == len(bar_options)
assert "bar stuff 1" == bar_options["STUFF_1"]
assert "bar stuff 2" == bar_options["STUFF_2"]
foo_options = app.config.get_namespace("FOO_", trim_namespace=False)
assert 2 == len(foo_options)
assert "foo option 1" == foo_options["foo_option_1"]
assert "foo option 2" == foo_options["foo_option_2"]
bar_options = app.config.get_namespace(
"BAR_", lowercase=False, trim_namespace=False
)
assert 2 == len(bar_options)
assert "bar stuff 1" == bar_options["BAR_STUFF_1"]
assert "bar stuff 2" == bar_options["BAR_STUFF_2"]
@pytest.mark.parametrize("encoding", ["utf-8", "iso-8859-15", "latin-1"])
def test_from_pyfile_weird_encoding(tmp_path, encoding):
f = tmp_path / "my_config.py"
f.write_text(f'# -*- coding: {encoding} -*-\nTEST_VALUE = "föö"\n', encoding)
app = flask.Flask(__name__)
app.config.from_pyfile(os.fspath(f))
value = app.config["TEST_VALUE"]
assert value == "föö"

View file

@ -1,42 +0,0 @@
from werkzeug.routing import BaseConverter
from flask import request
from flask import session
from flask import url_for
def test_custom_converters(app, client):
class ListConverter(BaseConverter):
def to_python(self, value):
return value.split(",")
def to_url(self, value):
base_to_url = super().to_url
return ",".join(base_to_url(x) for x in value)
app.url_map.converters["list"] = ListConverter
@app.route("/<list:args>")
def index(args):
return "|".join(args)
assert client.get("/1,2,3").data == b"1|2|3"
with app.test_request_context():
assert url_for("index", args=[4, 5, 6]) == "/4,5,6"
def test_context_available(app, client):
class ContextConverter(BaseConverter):
def to_python(self, value):
assert request is not None
assert session is not None
return value
app.url_map.converters["ctx"] = ContextConverter
@app.get("/<ctx:name>")
def index(name):
return name
assert client.get("/admin").data == b"admin"

29
tests/test_db.py Normal file
View file

@ -0,0 +1,29 @@
import sqlite3
import pytest
from flaskr.db import get_db
def test_get_close_db(app):
with app.app_context():
db = get_db()
assert db is get_db()
with pytest.raises(sqlite3.ProgrammingError) as e:
db.execute("SELECT 1")
assert "closed" in str(e.value)
def test_init_db_command(runner, monkeypatch):
class Recorder:
called = False
def fake_init_db():
Recorder.called = True
monkeypatch.setattr("flaskr.db.init_db", fake_init_db)
result = runner.invoke(args=["init-db"])
assert "Initialized" in result.output
assert Recorder.called

12
tests/test_factory.py Normal file
View file

@ -0,0 +1,12 @@
from flaskr import create_app
def test_config():
"""Test create_app without passing test config."""
assert not create_app().testing
assert create_app({"TESTING": True}).testing
def test_hello(client):
response = client.get("/hello")
assert response.data == b"Hello, World!"

View file

@ -1,360 +0,0 @@
import io
import os
import pytest
import werkzeug.exceptions
import flask
from flask.helpers import get_debug_flag
class FakePath:
"""Fake object to represent a ``PathLike object``.
This represents a ``pathlib.Path`` object in python 3.
See: https://www.python.org/dev/peps/pep-0519/
"""
def __init__(self, path):
self.path = path
def __fspath__(self):
return self.path
class PyBytesIO:
def __init__(self, *args, **kwargs):
self._io = io.BytesIO(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._io, name)
class TestSendfile:
def test_send_file(self, app, req_ctx):
rv = flask.send_file("static/index.html")
assert rv.direct_passthrough
assert rv.mimetype == "text/html"
with app.open_resource("static/index.html") as f:
rv.direct_passthrough = False
assert rv.data == f.read()
rv.close()
def test_static_file(self, app, req_ctx):
# Default max_age is None.
# Test with static file handler.
rv = app.send_static_file("index.html")
assert rv.cache_control.max_age is None
rv.close()
# Test with direct use of send_file.
rv = flask.send_file("static/index.html")
assert rv.cache_control.max_age is None
rv.close()
app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 3600
# Test with static file handler.
rv = app.send_static_file("index.html")
assert rv.cache_control.max_age == 3600
rv.close()
# Test with direct use of send_file.
rv = flask.send_file("static/index.html")
assert rv.cache_control.max_age == 3600
rv.close()
# Test with pathlib.Path.
rv = app.send_static_file(FakePath("index.html"))
assert rv.cache_control.max_age == 3600
rv.close()
class StaticFileApp(flask.Flask):
def get_send_file_max_age(self, filename):
return 10
app = StaticFileApp(__name__)
with app.test_request_context():
# Test with static file handler.
rv = app.send_static_file("index.html")
assert rv.cache_control.max_age == 10
rv.close()
# Test with direct use of send_file.
rv = flask.send_file("static/index.html")
assert rv.cache_control.max_age == 10
rv.close()
def test_send_from_directory(self, app, req_ctx):
app.root_path = os.path.join(
os.path.dirname(__file__), "test_apps", "subdomaintestmodule"
)
rv = flask.send_from_directory("static", "hello.txt")
rv.direct_passthrough = False
assert rv.data.strip() == b"Hello Subdomain"
rv.close()
class TestUrlFor:
def test_url_for_with_anchor(self, app, req_ctx):
@app.route("/")
def index():
return "42"
assert flask.url_for("index", _anchor="x y") == "/#x%20y"
def test_url_for_with_scheme(self, app, req_ctx):
@app.route("/")
def index():
return "42"
assert (
flask.url_for("index", _external=True, _scheme="https")
== "https://localhost/"
)
def test_url_for_with_scheme_not_external(self, app, req_ctx):
app.add_url_rule("/", endpoint="index")
# Implicit external with scheme.
url = flask.url_for("index", _scheme="https")
assert url == "https://localhost/"
# Error when external=False with scheme
with pytest.raises(ValueError):
flask.url_for("index", _scheme="https", _external=False)
def test_url_for_with_alternating_schemes(self, app, req_ctx):
@app.route("/")
def index():
return "42"
assert flask.url_for("index", _external=True) == "http://localhost/"
assert (
flask.url_for("index", _external=True, _scheme="https")
== "https://localhost/"
)
assert flask.url_for("index", _external=True) == "http://localhost/"
def test_url_with_method(self, app, req_ctx):
from flask.views import MethodView
class MyView(MethodView):
def get(self, id=None):
if id is None:
return "List"
return f"Get {id:d}"
def post(self):
return "Create"
myview = MyView.as_view("myview")
app.add_url_rule("/myview/", methods=["GET"], view_func=myview)
app.add_url_rule("/myview/<int:id>", methods=["GET"], view_func=myview)
app.add_url_rule("/myview/create", methods=["POST"], view_func=myview)
assert flask.url_for("myview", _method="GET") == "/myview/"
assert flask.url_for("myview", id=42, _method="GET") == "/myview/42"
assert flask.url_for("myview", _method="POST") == "/myview/create"
def test_url_for_with_self(self, app, req_ctx):
@app.route("/<self>")
def index(self):
return "42"
assert flask.url_for("index", self="2") == "/2"
def test_redirect_no_app():
response = flask.redirect("https://localhost", 307)
assert response.location == "https://localhost"
assert response.status_code == 307
def test_redirect_with_app(app):
def redirect(location, code=302):
raise ValueError
app.redirect = redirect
with app.app_context(), pytest.raises(ValueError):
flask.redirect("other")
def test_abort_no_app():
with pytest.raises(werkzeug.exceptions.Unauthorized):
flask.abort(401)
with pytest.raises(LookupError):
flask.abort(900)
def test_app_aborter_class():
class MyAborter(werkzeug.exceptions.Aborter):
pass
class MyFlask(flask.Flask):
aborter_class = MyAborter
app = MyFlask(__name__)
assert isinstance(app.aborter, MyAborter)
def test_abort_with_app(app):
class My900Error(werkzeug.exceptions.HTTPException):
code = 900
app.aborter.mapping[900] = My900Error
with app.app_context(), pytest.raises(My900Error):
flask.abort(900)
class TestNoImports:
"""Test Flasks are created without import.
Avoiding ``__import__`` helps create Flask instances where there are errors
at import time. Those runtime errors will be apparent to the user soon
enough, but tools which build Flask instances meta-programmatically benefit
from a Flask which does not ``__import__``. Instead of importing to
retrieve file paths or metadata on a module or package, use the pkgutil and
imp modules in the Python standard library.
"""
def test_name_with_import_error(self, modules_tmp_path):
(modules_tmp_path / "importerror.py").write_text("raise NotImplementedError()")
try:
flask.Flask("importerror")
except NotImplementedError:
AssertionError("Flask(import_name) is importing import_name.")
class TestStreaming:
def test_streaming_with_context(self, app, client):
@app.route("/")
def index():
def generate():
yield "Hello "
yield flask.request.args["name"]
yield "!"
return flask.Response(flask.stream_with_context(generate()))
rv = client.get("/?name=World")
assert rv.data == b"Hello World!"
def test_streaming_with_context_as_decorator(self, app, client):
@app.route("/")
def index():
@flask.stream_with_context
def generate(hello):
yield hello
yield flask.request.args["name"]
yield "!"
return flask.Response(generate("Hello "))
rv = client.get("/?name=World")
assert rv.data == b"Hello World!"
def test_streaming_with_context_and_custom_close(self, app, client):
called = []
class Wrapper:
def __init__(self, gen):
self._gen = gen
def __iter__(self):
return self
def close(self):
called.append(42)
def __next__(self):
return next(self._gen)
next = __next__
@app.route("/")
def index():
def generate():
yield "Hello "
yield flask.request.args["name"]
yield "!"
return flask.Response(flask.stream_with_context(Wrapper(generate())))
rv = client.get("/?name=World")
assert rv.data == b"Hello World!"
assert called == [42]
def test_stream_keeps_session(self, app, client):
@app.route("/")
def index():
flask.session["test"] = "flask"
@flask.stream_with_context
def gen():
yield flask.session["test"]
return flask.Response(gen())
rv = client.get("/")
assert rv.data == b"flask"
class TestHelpers:
@pytest.mark.parametrize(
("debug", "expect"),
[
("", False),
("0", False),
("False", False),
("No", False),
("True", True),
],
)
def test_get_debug_flag(self, monkeypatch, debug, expect):
monkeypatch.setenv("FLASK_DEBUG", debug)
assert get_debug_flag() == expect
def test_make_response(self):
app = flask.Flask(__name__)
with app.test_request_context():
rv = flask.helpers.make_response()
assert rv.status_code == 200
assert rv.mimetype == "text/html"
rv = flask.helpers.make_response("Hello")
assert rv.status_code == 200
assert rv.data == b"Hello"
assert rv.mimetype == "text/html"
@pytest.mark.parametrize("mode", ("r", "rb", "rt"))
def test_open_resource(mode):
app = flask.Flask(__name__)
with app.open_resource("static/index.html", mode) as f:
assert "<h1>Hello World!</h1>" in str(f.read())
@pytest.mark.parametrize("mode", ("w", "x", "a", "r+"))
def test_open_resource_exceptions(mode):
app = flask.Flask(__name__)
with pytest.raises(ValueError):
app.open_resource("static/index.html", mode)
@pytest.mark.parametrize("encoding", ("utf-8", "utf-16-le"))
def test_open_resource_with_encoding(tmp_path, encoding):
app = flask.Flask(__name__, root_path=os.fspath(tmp_path))
(tmp_path / "test").write_text("test", encoding=encoding)
with app.open_resource("test", mode="rt", encoding=encoding) as f:
assert f.read() == "test"

View file

@ -1,111 +0,0 @@
import os
import pytest
import flask
def test_explicit_instance_paths(modules_tmp_path):
with pytest.raises(ValueError, match=".*must be absolute"):
flask.Flask(__name__, instance_path="instance")
app = flask.Flask(__name__, instance_path=os.fspath(modules_tmp_path))
assert app.instance_path == os.fspath(modules_tmp_path)
def test_uninstalled_module_paths(modules_tmp_path, purge_module):
(modules_tmp_path / "config_module_app.py").write_text(
"import os\n"
"import flask\n"
"here = os.path.abspath(os.path.dirname(__file__))\n"
"app = flask.Flask(__name__)\n"
)
purge_module("config_module_app")
from config_module_app import app
assert app.instance_path == os.fspath(modules_tmp_path / "instance")
def test_uninstalled_package_paths(modules_tmp_path, purge_module):
app = modules_tmp_path / "config_package_app"
app.mkdir()
(app / "__init__.py").write_text(
"import os\n"
"import flask\n"
"here = os.path.abspath(os.path.dirname(__file__))\n"
"app = flask.Flask(__name__)\n"
)
purge_module("config_package_app")
from config_package_app import app
assert app.instance_path == os.fspath(modules_tmp_path / "instance")
def test_uninstalled_namespace_paths(tmp_path, monkeypatch, purge_module):
def create_namespace(package):
project = tmp_path / f"project-{package}"
monkeypatch.syspath_prepend(os.fspath(project))
ns = project / "namespace" / package
ns.mkdir(parents=True)
(ns / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n")
return project
_ = create_namespace("package1")
project2 = create_namespace("package2")
purge_module("namespace.package2")
purge_module("namespace")
from namespace.package2 import app
assert app.instance_path == os.fspath(project2 / "instance")
def test_installed_module_paths(
modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages, limit_loader
):
(site_packages / "site_app.py").write_text(
"import flask\napp = flask.Flask(__name__)\n"
)
purge_module("site_app")
from site_app import app
assert app.instance_path == os.fspath(
modules_tmp_path / "var" / "site_app-instance"
)
def test_installed_package_paths(
limit_loader, modules_tmp_path, modules_tmp_path_prefix, purge_module, monkeypatch
):
installed_path = modules_tmp_path / "path"
installed_path.mkdir()
monkeypatch.syspath_prepend(installed_path)
app = installed_path / "installed_package"
app.mkdir()
(app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n")
purge_module("installed_package")
from installed_package import app
assert app.instance_path == os.fspath(
modules_tmp_path / "var" / "installed_package-instance"
)
def test_prefix_package_paths(
limit_loader, modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages
):
app = site_packages / "site_package"
app.mkdir()
(app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n")
purge_module("site_package")
import site_package
assert site_package.app.instance_path == os.fspath(
modules_tmp_path / "var" / "site_package-instance"
)

View file

@ -1,346 +0,0 @@
import datetime
import decimal
import io
import uuid
import pytest
from werkzeug.http import http_date
import flask
from flask import json
from flask.json.provider import DefaultJSONProvider
@pytest.mark.parametrize("debug", (True, False))
def test_bad_request_debug_message(app, client, debug):
app.config["DEBUG"] = debug
app.config["TRAP_BAD_REQUEST_ERRORS"] = False
@app.route("/json", methods=["POST"])
def post_json():
flask.request.get_json()
return None
rv = client.post("/json", data=None, content_type="application/json")
assert rv.status_code == 400
contains = b"Failed to decode JSON object" in rv.data
assert contains == debug
def test_json_bad_requests(app, client):
@app.route("/json", methods=["POST"])
def return_json():
return flask.jsonify(foo=str(flask.request.get_json()))
rv = client.post("/json", data="malformed", content_type="application/json")
assert rv.status_code == 400
def test_json_custom_mimetypes(app, client):
@app.route("/json", methods=["POST"])
def return_json():
return flask.request.get_json()
rv = client.post("/json", data='"foo"', content_type="application/x+json")
assert rv.data == b"foo"
@pytest.mark.parametrize(
"test_value,expected", [(True, '"\\u2603"'), (False, '"\u2603"')]
)
def test_json_as_unicode(test_value, expected, app, app_ctx):
app.json.ensure_ascii = test_value
rv = app.json.dumps("\N{SNOWMAN}")
assert rv == expected
def test_json_dump_to_file(app, app_ctx):
test_data = {"name": "Flask"}
out = io.StringIO()
flask.json.dump(test_data, out)
out.seek(0)
rv = flask.json.load(out)
assert rv == test_data
@pytest.mark.parametrize(
"test_value", [0, -1, 1, 23, 3.14, "s", "longer string", True, False, None]
)
def test_jsonify_basic_types(test_value, app, client):
url = "/jsonify_basic_types"
app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x))
rv = client.get(url)
assert rv.mimetype == "application/json"
assert flask.json.loads(rv.data) == test_value
def test_jsonify_dicts(app, client):
d = {
"a": 0,
"b": 23,
"c": 3.14,
"d": "t",
"e": "Hi",
"f": True,
"g": False,
"h": ["test list", 10, False],
"i": {"test": "dict"},
}
@app.route("/kw")
def return_kwargs():
return flask.jsonify(**d)
@app.route("/dict")
def return_dict():
return flask.jsonify(d)
for url in "/kw", "/dict":
rv = client.get(url)
assert rv.mimetype == "application/json"
assert flask.json.loads(rv.data) == d
def test_jsonify_arrays(app, client):
"""Test jsonify of lists and args unpacking."""
a_list = [
0,
42,
3.14,
"t",
"hello",
True,
False,
["test list", 2, False],
{"test": "dict"},
]
@app.route("/args_unpack")
def return_args_unpack():
return flask.jsonify(*a_list)
@app.route("/array")
def return_array():
return flask.jsonify(a_list)
for url in "/args_unpack", "/array":
rv = client.get(url)
assert rv.mimetype == "application/json"
assert flask.json.loads(rv.data) == a_list
@pytest.mark.parametrize(
"value", [datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.date(1975, 1, 5)]
)
def test_jsonify_datetime(app, client, value):
@app.route("/")
def index():
return flask.jsonify(value=value)
r = client.get()
assert r.json["value"] == http_date(value)
class FixedOffset(datetime.tzinfo):
"""Fixed offset in hours east from UTC.
This is a slight adaptation of the ``FixedOffset`` example found in
https://docs.python.org/2.7/library/datetime.html.
"""
def __init__(self, hours, name):
self.__offset = datetime.timedelta(hours=hours)
self.__name = name
def utcoffset(self, dt):
return self.__offset
def tzname(self, dt):
return self.__name
def dst(self, dt):
return datetime.timedelta()
@pytest.mark.parametrize("tz", (("UTC", 0), ("PST", -8), ("KST", 9)))
def test_jsonify_aware_datetimes(tz):
"""Test if aware datetime.datetime objects are converted into GMT."""
tzinfo = FixedOffset(hours=tz[1], name=tz[0])
dt = datetime.datetime(2017, 1, 1, 12, 34, 56, tzinfo=tzinfo)
gmt = FixedOffset(hours=0, name="GMT")
expected = dt.astimezone(gmt).strftime('"%a, %d %b %Y %H:%M:%S %Z"')
assert flask.json.dumps(dt) == expected
def test_jsonify_uuid_types(app, client):
"""Test jsonify with uuid.UUID types"""
test_uuid = uuid.UUID(bytes=b"\xde\xad\xbe\xef" * 4)
url = "/uuid_test"
app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid))
rv = client.get(url)
rv_x = flask.json.loads(rv.data)["x"]
assert rv_x == str(test_uuid)
rv_uuid = uuid.UUID(rv_x)
assert rv_uuid == test_uuid
def test_json_decimal():
rv = flask.json.dumps(decimal.Decimal("0.003"))
assert rv == '"0.003"'
def test_json_attr(app, client):
@app.route("/add", methods=["POST"])
def add():
json = flask.request.get_json()
return str(json["a"] + json["b"])
rv = client.post(
"/add",
data=flask.json.dumps({"a": 1, "b": 2}),
content_type="application/json",
)
assert rv.data == b"3"
def test_tojson_filter(app, req_ctx):
# The tojson filter is tested in Jinja, this confirms that it's
# using Flask's dumps.
rv = flask.render_template_string(
"const data = {{ data|tojson }};",
data={"name": "</script>", "time": datetime.datetime(2021, 2, 1, 7, 15)},
)
assert rv == (
'const data = {"name": "\\u003c/script\\u003e",'
' "time": "Mon, 01 Feb 2021 07:15:00 GMT"};'
)
def test_json_customization(app, client):
class X: # noqa: B903, for Python2 compatibility
def __init__(self, val):
self.val = val
def default(o):
if isinstance(o, X):
return f"<{o.val}>"
return DefaultJSONProvider.default(o)
class CustomProvider(DefaultJSONProvider):
def object_hook(self, obj):
if len(obj) == 1 and "_foo" in obj:
return X(obj["_foo"])
return obj
def loads(self, s, **kwargs):
kwargs.setdefault("object_hook", self.object_hook)
return super().loads(s, **kwargs)
app.json = CustomProvider(app)
app.json.default = default
@app.route("/", methods=["POST"])
def index():
return flask.json.dumps(flask.request.get_json()["x"])
rv = client.post(
"/",
data=flask.json.dumps({"x": {"_foo": 42}}),
content_type="application/json",
)
assert rv.data == b'"<42>"'
def _has_encoding(name):
try:
import codecs
codecs.lookup(name)
return True
except LookupError:
return False
def test_json_key_sorting(app, client):
app.debug = True
assert app.json.sort_keys
d = dict.fromkeys(range(20), "foo")
@app.route("/")
def index():
return flask.jsonify(values=d)
rv = client.get("/")
lines = [x.strip() for x in rv.data.strip().decode("utf-8").splitlines()]
sorted_by_str = [
"{",
'"values": {',
'"0": "foo",',
'"1": "foo",',
'"10": "foo",',
'"11": "foo",',
'"12": "foo",',
'"13": "foo",',
'"14": "foo",',
'"15": "foo",',
'"16": "foo",',
'"17": "foo",',
'"18": "foo",',
'"19": "foo",',
'"2": "foo",',
'"3": "foo",',
'"4": "foo",',
'"5": "foo",',
'"6": "foo",',
'"7": "foo",',
'"8": "foo",',
'"9": "foo"',
"}",
"}",
]
sorted_by_int = [
"{",
'"values": {',
'"0": "foo",',
'"1": "foo",',
'"2": "foo",',
'"3": "foo",',
'"4": "foo",',
'"5": "foo",',
'"6": "foo",',
'"7": "foo",',
'"8": "foo",',
'"9": "foo",',
'"10": "foo",',
'"11": "foo",',
'"12": "foo",',
'"13": "foo",',
'"14": "foo",',
'"15": "foo",',
'"16": "foo",',
'"17": "foo",',
'"18": "foo",',
'"19": "foo"',
"}",
"}",
]
try:
assert lines == sorted_by_int
except AssertionError:
assert lines == sorted_by_str
def test_html_method():
class ObjectWithHTML:
def __html__(self):
return "<p>test</p>"
result = json.dumps(ObjectWithHTML())
assert result == '"<p>test</p>"'

View file

@ -1,86 +0,0 @@
from datetime import datetime
from datetime import timezone
from uuid import uuid4
import pytest
from markupsafe import Markup
from flask.json.tag import JSONTag
from flask.json.tag import TaggedJSONSerializer
@pytest.mark.parametrize(
"data",
(
{" t": (1, 2, 3)},
{" t__": b"a"},
{" di": " di"},
{"x": (1, 2, 3), "y": 4},
(1, 2, 3),
[(1, 2, 3)],
b"\xff",
Markup("<html>"),
uuid4(),
datetime.now(tz=timezone.utc).replace(microsecond=0),
),
)
def test_dump_load_unchanged(data):
s = TaggedJSONSerializer()
assert s.loads(s.dumps(data)) == data
def test_duplicate_tag():
class TagDict(JSONTag):
key = " d"
s = TaggedJSONSerializer()
pytest.raises(KeyError, s.register, TagDict)
s.register(TagDict, force=True, index=0)
assert isinstance(s.tags[" d"], TagDict)
assert isinstance(s.order[0], TagDict)
def test_custom_tag():
class Foo: # noqa: B903, for Python2 compatibility
def __init__(self, data):
self.data = data
class TagFoo(JSONTag):
__slots__ = ()
key = " f"
def check(self, value):
return isinstance(value, Foo)
def to_json(self, value):
return self.serializer.tag(value.data)
def to_python(self, value):
return Foo(value)
s = TaggedJSONSerializer()
s.register(TagFoo)
assert s.loads(s.dumps(Foo("bar"))).data == "bar"
def test_tag_interface():
t = JSONTag(None)
pytest.raises(NotImplementedError, t.check, None)
pytest.raises(NotImplementedError, t.to_json, None)
pytest.raises(NotImplementedError, t.to_python, None)
def test_tag_order():
class Tag1(JSONTag):
key = " 1"
class Tag2(JSONTag):
key = " 2"
s = TaggedJSONSerializer()
s.register(Tag1, index=-1)
assert isinstance(s.order[-2], Tag1)
s.register(Tag2, index=None)
assert isinstance(s.order[-1], Tag2)

View file

@ -1,98 +0,0 @@
import logging
import sys
from io import StringIO
import pytest
from flask.logging import default_handler
from flask.logging import has_level_handler
from flask.logging import wsgi_errors_stream
@pytest.fixture(autouse=True)
def reset_logging(pytestconfig):
root_handlers = logging.root.handlers[:]
logging.root.handlers = []
root_level = logging.root.level
logger = logging.getLogger("flask_test")
logger.handlers = []
logger.setLevel(logging.NOTSET)
logging_plugin = pytestconfig.pluginmanager.unregister(name="logging-plugin")
yield
logging.root.handlers[:] = root_handlers
logging.root.setLevel(root_level)
logger.handlers = []
logger.setLevel(logging.NOTSET)
if logging_plugin:
pytestconfig.pluginmanager.register(logging_plugin, "logging-plugin")
def test_logger(app):
assert app.logger.name == "flask_test"
assert app.logger.level == logging.NOTSET
assert app.logger.handlers == [default_handler]
def test_logger_debug(app):
app.debug = True
assert app.logger.level == logging.DEBUG
assert app.logger.handlers == [default_handler]
def test_existing_handler(app):
logging.root.addHandler(logging.StreamHandler())
assert app.logger.level == logging.NOTSET
assert not app.logger.handlers
def test_wsgi_errors_stream(app, client):
@app.route("/")
def index():
app.logger.error("test")
return ""
stream = StringIO()
client.get("/", errors_stream=stream)
assert "ERROR in test_logging: test" in stream.getvalue()
assert wsgi_errors_stream._get_current_object() is sys.stderr
with app.test_request_context(errors_stream=stream):
assert wsgi_errors_stream._get_current_object() is stream
def test_has_level_handler():
logger = logging.getLogger("flask.app")
assert not has_level_handler(logger)
handler = logging.StreamHandler()
logging.root.addHandler(handler)
assert has_level_handler(logger)
logger.propagate = False
assert not has_level_handler(logger)
logger.propagate = True
handler.setLevel(logging.ERROR)
assert not has_level_handler(logger)
def test_log_view_exception(app, client):
@app.route("/")
def index():
raise Exception("test")
app.testing = False
stream = StringIO()
rv = client.get("/", errors_stream=stream)
assert rv.status_code == 500
assert rv.data
err = stream.getvalue()
assert "Exception on / [GET]" in err
assert "Exception: test" in err

View file

@ -1,30 +0,0 @@
import flask
def test_aborting(app):
class Foo(Exception):
whatever = 42
@app.errorhandler(Foo)
def handle_foo(e):
return str(e.whatever)
@app.route("/")
def index():
raise flask.abort(flask.redirect(flask.url_for("test")))
@app.route("/test")
def test():
raise Foo()
with app.test_client() as c:
rv = c.get("/")
location_parts = rv.headers["Location"].rpartition("/")
if location_parts[0]:
# For older Werkzeug that used absolute redirects.
assert location_parts[0] == "http://localhost"
assert location_parts[2] == "test"
rv = c.get("/test")
assert rv.data == b"42"

View file

@ -1,325 +0,0 @@
import warnings
import pytest
import flask
from flask.globals import request_ctx
from flask.sessions import SecureCookieSessionInterface
from flask.sessions import SessionInterface
try:
from greenlet import greenlet
except ImportError:
greenlet = None
def test_teardown_on_pop(app):
buffer = []
@app.teardown_request
def end_of_request(exception):
buffer.append(exception)
ctx = app.test_request_context()
ctx.push()
assert buffer == []
ctx.pop()
assert buffer == [None]
def test_teardown_with_previous_exception(app):
buffer = []
@app.teardown_request
def end_of_request(exception):
buffer.append(exception)
try:
raise Exception("dummy")
except Exception:
pass
with app.test_request_context():
assert buffer == []
assert buffer == [None]
def test_teardown_with_handled_exception(app):
buffer = []
@app.teardown_request
def end_of_request(exception):
buffer.append(exception)
with app.test_request_context():
assert buffer == []
try:
raise Exception("dummy")
except Exception:
pass
assert buffer == [None]
def test_proper_test_request_context(app):
app.config.update(SERVER_NAME="localhost.localdomain:5000")
@app.route("/")
def index():
return None
@app.route("/", subdomain="foo")
def sub():
return None
with app.test_request_context("/"):
assert (
flask.url_for("index", _external=True)
== "http://localhost.localdomain:5000/"
)
with app.test_request_context("/"):
assert (
flask.url_for("sub", _external=True)
== "http://foo.localhost.localdomain:5000/"
)
# suppress Werkzeug 0.15 warning about name mismatch
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", "Current server name", UserWarning, "flask.app"
)
with app.test_request_context(
"/", environ_overrides={"HTTP_HOST": "localhost"}
):
pass
app.config.update(SERVER_NAME="localhost")
with app.test_request_context("/", environ_overrides={"SERVER_NAME": "localhost"}):
pass
app.config.update(SERVER_NAME="localhost:80")
with app.test_request_context(
"/", environ_overrides={"SERVER_NAME": "localhost:80"}
):
pass
def test_context_binding(app):
@app.route("/")
def index():
return f"Hello {flask.request.args['name']}!"
@app.route("/meh")
def meh():
return flask.request.url
with app.test_request_context("/?name=World"):
assert index() == "Hello World!"
with app.test_request_context("/meh"):
assert meh() == "http://localhost/meh"
assert not flask.request
def test_context_test(app):
assert not flask.request
assert not flask.has_request_context()
ctx = app.test_request_context()
ctx.push()
try:
assert flask.request
assert flask.has_request_context()
finally:
ctx.pop()
def test_manual_context_binding(app):
@app.route("/")
def index():
return f"Hello {flask.request.args['name']}!"
ctx = app.test_request_context("/?name=World")
ctx.push()
assert index() == "Hello World!"
ctx.pop()
with pytest.raises(RuntimeError):
index()
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
class TestGreenletContextCopying:
def test_greenlet_context_copying(self, app, client):
greenlets = []
@app.route("/")
def index():
flask.session["fizz"] = "buzz"
reqctx = request_ctx.copy()
def g():
assert not flask.request
assert not flask.current_app
with reqctx:
assert flask.request
assert flask.current_app == app
assert flask.request.path == "/"
assert flask.request.args["foo"] == "bar"
assert flask.session.get("fizz") == "buzz"
assert not flask.request
return 42
greenlets.append(greenlet(g))
return "Hello World!"
rv = client.get("/?foo=bar")
assert rv.data == b"Hello World!"
result = greenlets[0].run()
assert result == 42
def test_greenlet_context_copying_api(self, app, client):
greenlets = []
@app.route("/")
def index():
flask.session["fizz"] = "buzz"
@flask.copy_current_request_context
def g():
assert flask.request
assert flask.current_app == app
assert flask.request.path == "/"
assert flask.request.args["foo"] == "bar"
assert flask.session.get("fizz") == "buzz"
return 42
greenlets.append(greenlet(g))
return "Hello World!"
rv = client.get("/?foo=bar")
assert rv.data == b"Hello World!"
result = greenlets[0].run()
assert result == 42
def test_session_error_pops_context():
class SessionError(Exception):
pass
class FailingSessionInterface(SessionInterface):
def open_session(self, app, request):
raise SessionError()
class CustomFlask(flask.Flask):
session_interface = FailingSessionInterface()
app = CustomFlask(__name__)
@app.route("/")
def index():
# shouldn't get here
AssertionError()
response = app.test_client().get("/")
assert response.status_code == 500
assert not flask.request
assert not flask.current_app
def test_session_dynamic_cookie_name():
# This session interface will use a cookie with a different name if the
# requested url ends with the string "dynamic_cookie"
class PathAwareSessionInterface(SecureCookieSessionInterface):
def get_cookie_name(self, app):
if flask.request.url.endswith("dynamic_cookie"):
return "dynamic_cookie_name"
else:
return super().get_cookie_name(app)
class CustomFlask(flask.Flask):
session_interface = PathAwareSessionInterface()
app = CustomFlask(__name__)
app.secret_key = "secret_key"
@app.route("/set", methods=["POST"])
def set():
flask.session["value"] = flask.request.form["value"]
return "value set"
@app.route("/get")
def get():
v = flask.session.get("value", "None")
return v
@app.route("/set_dynamic_cookie", methods=["POST"])
def set_dynamic_cookie():
flask.session["value"] = flask.request.form["value"]
return "value set"
@app.route("/get_dynamic_cookie")
def get_dynamic_cookie():
v = flask.session.get("value", "None")
return v
test_client = app.test_client()
# first set the cookie in both /set urls but each with a different value
assert test_client.post("/set", data={"value": "42"}).data == b"value set"
assert (
test_client.post("/set_dynamic_cookie", data={"value": "616"}).data
== b"value set"
)
# now check that the relevant values come back - meaning that different
# cookies are being used for the urls that end with "dynamic cookie"
assert test_client.get("/get").data == b"42"
assert test_client.get("/get_dynamic_cookie").data == b"616"
def test_bad_environ_raises_bad_request():
app = flask.Flask(__name__)
from flask.testing import EnvironBuilder
builder = EnvironBuilder(app)
environ = builder.get_environ()
# use a non-printable character in the Host - this is key to this test
environ["HTTP_HOST"] = "\x8a"
with app.request_context(environ):
response = app.full_dispatch_request()
assert response.status_code == 400
def test_environ_for_valid_idna_completes():
app = flask.Flask(__name__)
@app.route("/")
def index():
return "Hello World!"
from flask.testing import EnvironBuilder
builder = EnvironBuilder(app)
environ = builder.get_environ()
# these characters are all IDNA-compatible
environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
with app.request_context(environ):
response = app.full_dispatch_request()
assert response.status_code == 200
def test_normal_environ_completes():
app = flask.Flask(__name__)
@app.route("/")
def index():
return "Hello World!"
response = app.test_client().get("/", headers={"host": "xn--on-0ia.com"})
assert response.status_code == 200

View file

@ -1,28 +0,0 @@
import flask
from flask.globals import request_ctx
from flask.sessions import SessionInterface
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, app, session, response):
pass
def open_session(self, app, request):
request_ctx.match_request()
assert request.endpoint is not None
app = flask.Flask(__name__)
app.session_interface = MySessionInterface()
@app.get("/")
def index():
return "Hello, World!"
response = app.test_client().get("/")
assert response.status_code == 200

View file

@ -1,181 +0,0 @@
import flask
def test_template_rendered(app, client):
@app.route("/")
def index():
return flask.render_template("simple_template.html", whiskey=42)
recorded = []
def record(sender, template, context):
recorded.append((template, context))
flask.template_rendered.connect(record, app)
try:
client.get("/")
assert len(recorded) == 1
template, context = recorded[0]
assert template.name == "simple_template.html"
assert context["whiskey"] == 42
finally:
flask.template_rendered.disconnect(record, app)
def test_before_render_template():
app = flask.Flask(__name__)
@app.route("/")
def index():
return flask.render_template("simple_template.html", whiskey=42)
recorded = []
def record(sender, template, context):
context["whiskey"] = 43
recorded.append((template, context))
flask.before_render_template.connect(record, app)
try:
rv = app.test_client().get("/")
assert len(recorded) == 1
template, context = recorded[0]
assert template.name == "simple_template.html"
assert context["whiskey"] == 43
assert rv.data == b"<h1>43</h1>"
finally:
flask.before_render_template.disconnect(record, app)
def test_request_signals():
app = flask.Flask(__name__)
calls = []
def before_request_signal(sender):
calls.append("before-signal")
def after_request_signal(sender, response):
assert response.data == b"stuff"
calls.append("after-signal")
@app.before_request
def before_request_handler():
calls.append("before-handler")
@app.after_request
def after_request_handler(response):
calls.append("after-handler")
response.data = "stuff"
return response
@app.route("/")
def index():
calls.append("handler")
return "ignored anyway"
flask.request_started.connect(before_request_signal, app)
flask.request_finished.connect(after_request_signal, app)
try:
rv = app.test_client().get("/")
assert rv.data == b"stuff"
assert calls == [
"before-signal",
"before-handler",
"handler",
"after-handler",
"after-signal",
]
finally:
flask.request_started.disconnect(before_request_signal, app)
flask.request_finished.disconnect(after_request_signal, app)
def test_request_exception_signal():
app = flask.Flask(__name__)
recorded = []
@app.route("/")
def index():
raise ZeroDivisionError
def record(sender, exception):
recorded.append(exception)
flask.got_request_exception.connect(record, app)
try:
assert app.test_client().get("/").status_code == 500
assert len(recorded) == 1
assert isinstance(recorded[0], ZeroDivisionError)
finally:
flask.got_request_exception.disconnect(record, app)
def test_appcontext_signals(app, client):
recorded = []
def record_push(sender, **kwargs):
recorded.append("push")
def record_pop(sender, **kwargs):
recorded.append("pop")
@app.route("/")
def index():
return "Hello"
flask.appcontext_pushed.connect(record_push, app)
flask.appcontext_popped.connect(record_pop, app)
try:
rv = client.get("/")
assert rv.data == b"Hello"
assert recorded == ["push", "pop"]
finally:
flask.appcontext_pushed.disconnect(record_push, app)
flask.appcontext_popped.disconnect(record_pop, app)
def test_flash_signal(app):
@app.route("/")
def index():
flask.flash("This is a flash message", category="notice")
return flask.redirect("/other")
recorded = []
def record(sender, message, category):
recorded.append((message, category))
flask.message_flashed.connect(record, app)
try:
client = app.test_client()
with client.session_transaction():
client.get("/")
assert len(recorded) == 1
message, category = recorded[0]
assert message == "This is a flash message"
assert category == "notice"
finally:
flask.message_flashed.disconnect(record, app)
def test_appcontext_tearing_down_signal(app, client):
app.testing = False
recorded = []
def record_teardown(sender, exc):
recorded.append(exc)
@app.route("/")
def index():
raise ZeroDivisionError
flask.appcontext_tearing_down.connect(record_teardown, app)
try:
rv = client.get("/")
assert rv.status_code == 500
assert len(recorded) == 1
assert isinstance(recorded[0], ZeroDivisionError)
finally:
flask.appcontext_tearing_down.disconnect(record_teardown, app)

View file

@ -1,21 +0,0 @@
from io import StringIO
import flask
def test_suppressed_exception_logging():
class SuppressedFlask(flask.Flask):
def log_exception(self, exc_info):
pass
out = StringIO()
app = SuppressedFlask(__name__)
@app.route("/")
def index():
raise Exception("test")
rv = app.test_client().get("/", errors_stream=out)
assert rv.status_code == 500
assert b"Internal Server Error" in rv.data
assert not out.getvalue()

View file

@ -1,451 +0,0 @@
import logging
import pytest
import werkzeug.serving
from jinja2 import TemplateNotFound
from markupsafe import Markup
import flask
def test_context_processing(app, client):
@app.context_processor
def context_processor():
return {"injected_value": 42}
@app.route("/")
def index():
return flask.render_template("context_template.html", value=23)
rv = client.get("/")
assert rv.data == b"<p>23|42"
def test_original_win(app, client):
@app.route("/")
def index():
return flask.render_template_string("{{ config }}", config=42)
rv = client.get("/")
assert rv.data == b"42"
def test_simple_stream(app, client):
@app.route("/")
def index():
return flask.stream_template_string("{{ config }}", config=42)
rv = client.get("/")
assert rv.data == b"42"
def test_request_less_rendering(app, app_ctx):
app.config["WORLD_NAME"] = "Special World"
@app.context_processor
def context_processor():
return dict(foo=42)
rv = flask.render_template_string("Hello {{ config.WORLD_NAME }} {{ foo }}")
assert rv == "Hello Special World 42"
def test_standard_context(app, client):
@app.route("/")
def index():
flask.g.foo = 23
flask.session["test"] = "aha"
return flask.render_template_string(
"""
{{ request.args.foo }}
{{ g.foo }}
{{ config.DEBUG }}
{{ session.test }}
"""
)
rv = client.get("/?foo=42")
assert rv.data.split() == [b"42", b"23", b"False", b"aha"]
def test_escaping(app, client):
text = "<p>Hello World!"
@app.route("/")
def index():
return flask.render_template(
"escaping_template.html", text=text, html=Markup(text)
)
lines = client.get("/").data.splitlines()
assert lines == [
b"&lt;p&gt;Hello World!",
b"<p>Hello World!",
b"<p>Hello World!",
b"<p>Hello World!",
b"&lt;p&gt;Hello World!",
b"<p>Hello World!",
]
def test_no_escaping(app, client):
text = "<p>Hello World!"
@app.route("/")
def index():
return flask.render_template(
"non_escaping_template.txt", text=text, html=Markup(text)
)
lines = client.get("/").data.splitlines()
assert lines == [
b"<p>Hello World!",
b"<p>Hello World!",
b"<p>Hello World!",
b"<p>Hello World!",
b"&lt;p&gt;Hello World!",
b"<p>Hello World!",
b"<p>Hello World!",
b"<p>Hello World!",
]
def test_escaping_without_template_filename(app, client, req_ctx):
assert flask.render_template_string("{{ foo }}", foo="<test>") == "&lt;test&gt;"
assert flask.render_template("mail.txt", foo="<test>") == "<test> Mail"
def test_macros(app, req_ctx):
macro = flask.get_template_attribute("_macro.html", "hello")
assert macro("World") == "Hello World!"
def test_template_filter(app):
@app.template_filter()
def my_reverse(s):
return s[::-1]
assert "my_reverse" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse"] == my_reverse
assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
def test_add_template_filter(app):
def my_reverse(s):
return s[::-1]
app.add_template_filter(my_reverse)
assert "my_reverse" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse"] == my_reverse
assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
def test_template_filter_with_name(app):
@app.template_filter("strrev")
def my_reverse(s):
return s[::-1]
assert "strrev" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["strrev"] == my_reverse
assert app.jinja_env.filters["strrev"]("abcd") == "dcba"
def test_add_template_filter_with_name(app):
def my_reverse(s):
return s[::-1]
app.add_template_filter(my_reverse, "strrev")
assert "strrev" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["strrev"] == my_reverse
assert app.jinja_env.filters["strrev"]("abcd") == "dcba"
def test_template_filter_with_template(app, client):
@app.template_filter()
def super_reverse(s):
return s[::-1]
@app.route("/")
def index():
return flask.render_template("template_filter.html", value="abcd")
rv = client.get("/")
assert rv.data == b"dcba"
def test_add_template_filter_with_template(app, client):
def super_reverse(s):
return s[::-1]
app.add_template_filter(super_reverse)
@app.route("/")
def index():
return flask.render_template("template_filter.html", value="abcd")
rv = client.get("/")
assert rv.data == b"dcba"
def test_template_filter_with_name_and_template(app, client):
@app.template_filter("super_reverse")
def my_reverse(s):
return s[::-1]
@app.route("/")
def index():
return flask.render_template("template_filter.html", value="abcd")
rv = client.get("/")
assert rv.data == b"dcba"
def test_add_template_filter_with_name_and_template(app, client):
def my_reverse(s):
return s[::-1]
app.add_template_filter(my_reverse, "super_reverse")
@app.route("/")
def index():
return flask.render_template("template_filter.html", value="abcd")
rv = client.get("/")
assert rv.data == b"dcba"
def test_template_test(app):
@app.template_test()
def boolean(value):
return isinstance(value, bool)
assert "boolean" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["boolean"] == boolean
assert app.jinja_env.tests["boolean"](False)
def test_add_template_test(app):
def boolean(value):
return isinstance(value, bool)
app.add_template_test(boolean)
assert "boolean" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["boolean"] == boolean
assert app.jinja_env.tests["boolean"](False)
def test_template_test_with_name(app):
@app.template_test("boolean")
def is_boolean(value):
return isinstance(value, bool)
assert "boolean" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["boolean"] == is_boolean
assert app.jinja_env.tests["boolean"](False)
def test_add_template_test_with_name(app):
def is_boolean(value):
return isinstance(value, bool)
app.add_template_test(is_boolean, "boolean")
assert "boolean" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["boolean"] == is_boolean
assert app.jinja_env.tests["boolean"](False)
def test_template_test_with_template(app, client):
@app.template_test()
def boolean(value):
return isinstance(value, bool)
@app.route("/")
def index():
return flask.render_template("template_test.html", value=False)
rv = client.get("/")
assert b"Success!" in rv.data
def test_add_template_test_with_template(app, client):
def boolean(value):
return isinstance(value, bool)
app.add_template_test(boolean)
@app.route("/")
def index():
return flask.render_template("template_test.html", value=False)
rv = client.get("/")
assert b"Success!" in rv.data
def test_template_test_with_name_and_template(app, client):
@app.template_test("boolean")
def is_boolean(value):
return isinstance(value, bool)
@app.route("/")
def index():
return flask.render_template("template_test.html", value=False)
rv = client.get("/")
assert b"Success!" in rv.data
def test_add_template_test_with_name_and_template(app, client):
def is_boolean(value):
return isinstance(value, bool)
app.add_template_test(is_boolean, "boolean")
@app.route("/")
def index():
return flask.render_template("template_test.html", value=False)
rv = client.get("/")
assert b"Success!" in rv.data
def test_add_template_global(app, app_ctx):
@app.template_global()
def get_stuff():
return 42
assert "get_stuff" in app.jinja_env.globals.keys()
assert app.jinja_env.globals["get_stuff"] == get_stuff
assert app.jinja_env.globals["get_stuff"](), 42
rv = flask.render_template_string("{{ get_stuff() }}")
assert rv == "42"
def test_custom_template_loader(client):
class MyFlask(flask.Flask):
def create_global_jinja_loader(self):
from jinja2 import DictLoader
return DictLoader({"index.html": "Hello Custom World!"})
app = MyFlask(__name__)
@app.route("/")
def index():
return flask.render_template("index.html")
c = app.test_client()
rv = c.get("/")
assert rv.data == b"Hello Custom World!"
def test_iterable_loader(app, client):
@app.context_processor
def context_processor():
return {"whiskey": "Jameson"}
@app.route("/")
def index():
return flask.render_template(
[
"no_template.xml", # should skip this one
"simple_template.html", # should render this
"context_template.html",
],
value=23,
)
rv = client.get("/")
assert rv.data == b"<h1>Jameson</h1>"
def test_templates_auto_reload(app):
# debug is False, config option is None
assert app.debug is False
assert app.config["TEMPLATES_AUTO_RELOAD"] is None
assert app.jinja_env.auto_reload is False
# debug is False, config option is False
app = flask.Flask(__name__)
app.config["TEMPLATES_AUTO_RELOAD"] = False
assert app.debug is False
assert app.jinja_env.auto_reload is False
# debug is False, config option is True
app = flask.Flask(__name__)
app.config["TEMPLATES_AUTO_RELOAD"] = True
assert app.debug is False
assert app.jinja_env.auto_reload is True
# debug is True, config option is None
app = flask.Flask(__name__)
app.config["DEBUG"] = True
assert app.config["TEMPLATES_AUTO_RELOAD"] is None
assert app.jinja_env.auto_reload is True
# debug is True, config option is False
app = flask.Flask(__name__)
app.config["DEBUG"] = True
app.config["TEMPLATES_AUTO_RELOAD"] = False
assert app.jinja_env.auto_reload is False
# debug is True, config option is True
app = flask.Flask(__name__)
app.config["DEBUG"] = True
app.config["TEMPLATES_AUTO_RELOAD"] = True
assert app.jinja_env.auto_reload is True
def test_templates_auto_reload_debug_run(app, monkeypatch):
def run_simple_mock(*args, **kwargs):
pass
monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
app.run()
assert not app.jinja_env.auto_reload
app.run(debug=True)
assert app.jinja_env.auto_reload
def test_template_loader_debugging(test_apps, monkeypatch):
from blueprintapp import app
called = []
class _TestHandler(logging.Handler):
def handle(self, record):
called.append(True)
text = str(record.msg)
assert "1: trying loader of application 'blueprintapp'" in text
assert (
"2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)"
) in text
assert (
"trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)"
) in text
assert "Error: the template could not be found" in text
assert (
"looked up from an endpoint that belongs to the blueprint 'frontend'"
) in text
assert "See https://flask.palletsprojects.com/blueprints/#templates" in text
with app.test_client() as c:
monkeypatch.setitem(app.config, "EXPLAIN_TEMPLATE_LOADING", True)
monkeypatch.setattr(
logging.getLogger("blueprintapp"), "handlers", [_TestHandler()]
)
with pytest.raises(TemplateNotFound) as excinfo:
c.get("/missing")
assert "missing_template.html" in str(excinfo.value)
assert len(called) == 1
def test_custom_jinja_env():
class CustomEnvironment(flask.templating.Environment):
pass
class CustomFlask(flask.Flask):
jinja_environment = CustomEnvironment
app = CustomFlask(__name__)
assert isinstance(app.jinja_env, CustomEnvironment)

View file

@ -1,396 +0,0 @@
import importlib.metadata
import click
import pytest
import flask
from flask import appcontext_popped
from flask.cli import ScriptInfo
from flask.globals import _cv_request
from flask.json import jsonify
from flask.testing import EnvironBuilder
from flask.testing import FlaskCliRunner
def test_environ_defaults_from_config(app, client):
app.config["SERVER_NAME"] = "example.com:1234"
app.config["APPLICATION_ROOT"] = "/foo"
@app.route("/")
def index():
return flask.request.url
ctx = app.test_request_context()
assert ctx.request.url == "http://example.com:1234/foo/"
rv = client.get("/")
assert rv.data == b"http://example.com:1234/foo/"
def test_environ_defaults(app, client, app_ctx, req_ctx):
@app.route("/")
def index():
return flask.request.url
ctx = app.test_request_context()
assert ctx.request.url == "http://localhost/"
with client:
rv = client.get("/")
assert rv.data == b"http://localhost/"
def test_environ_base_default(app, client):
@app.route("/")
def index():
flask.g.remote_addr = flask.request.remote_addr
flask.g.user_agent = flask.request.user_agent.string
return ""
with client:
client.get("/")
assert flask.g.remote_addr == "127.0.0.1"
assert flask.g.user_agent == (
f"Werkzeug/{importlib.metadata.version('werkzeug')}"
)
def test_environ_base_modified(app, client):
@app.route("/")
def index():
flask.g.remote_addr = flask.request.remote_addr
flask.g.user_agent = flask.request.user_agent.string
return ""
client.environ_base["REMOTE_ADDR"] = "192.168.0.22"
client.environ_base["HTTP_USER_AGENT"] = "Foo"
with client:
client.get("/")
assert flask.g.remote_addr == "192.168.0.22"
assert flask.g.user_agent == "Foo"
def test_client_open_environ(app, client, request):
@app.route("/index")
def index():
return flask.request.remote_addr
builder = EnvironBuilder(app, path="/index", method="GET")
request.addfinalizer(builder.close)
rv = client.open(builder)
assert rv.data == b"127.0.0.1"
environ = builder.get_environ()
client.environ_base["REMOTE_ADDR"] = "127.0.0.2"
rv = client.open(environ)
assert rv.data == b"127.0.0.2"
def test_specify_url_scheme(app, client):
@app.route("/")
def index():
return flask.request.url
ctx = app.test_request_context(url_scheme="https")
assert ctx.request.url == "https://localhost/"
rv = client.get("/", url_scheme="https")
assert rv.data == b"https://localhost/"
def test_path_is_url(app):
eb = EnvironBuilder(app, "https://example.com/")
assert eb.url_scheme == "https"
assert eb.host == "example.com"
assert eb.script_root == ""
assert eb.path == "/"
def test_environbuilder_json_dumps(app):
"""EnvironBuilder.json_dumps() takes settings from the app."""
app.json.ensure_ascii = False
eb = EnvironBuilder(app, json="\u20ac")
assert eb.input_stream.read().decode("utf8") == '"\u20ac"'
def test_blueprint_with_subdomain():
app = flask.Flask(__name__, subdomain_matching=True)
app.config["SERVER_NAME"] = "example.com:1234"
app.config["APPLICATION_ROOT"] = "/foo"
client = app.test_client()
bp = flask.Blueprint("company", __name__, subdomain="xxx")
@bp.route("/")
def index():
return flask.request.url
app.register_blueprint(bp)
ctx = app.test_request_context("/", subdomain="xxx")
assert ctx.request.url == "http://xxx.example.com:1234/foo/"
with ctx:
assert ctx.request.blueprint == bp.name
rv = client.get("/", subdomain="xxx")
assert rv.data == b"http://xxx.example.com:1234/foo/"
def test_redirect_keep_session(app, client, app_ctx):
@app.route("/", methods=["GET", "POST"])
def index():
if flask.request.method == "POST":
return flask.redirect("/getsession")
flask.session["data"] = "foo"
return "index"
@app.route("/getsession")
def get_session():
return flask.session.get("data", "<missing>")
with client:
rv = client.get("/getsession")
assert rv.data == b"<missing>"
rv = client.get("/")
assert rv.data == b"index"
assert flask.session.get("data") == "foo"
rv = client.post("/", data={}, follow_redirects=True)
assert rv.data == b"foo"
assert flask.session.get("data") == "foo"
rv = client.get("/getsession")
assert rv.data == b"foo"
def test_session_transactions(app, client):
@app.route("/")
def index():
return str(flask.session["foo"])
with client:
with client.session_transaction() as sess:
assert len(sess) == 0
sess["foo"] = [42]
assert len(sess) == 1
rv = client.get("/")
assert rv.data == b"[42]"
with client.session_transaction() as sess:
assert len(sess) == 1
assert sess["foo"] == [42]
def test_session_transactions_no_null_sessions():
app = flask.Flask(__name__)
with app.test_client() as c:
with pytest.raises(RuntimeError) as e:
with c.session_transaction():
pass
assert "Session backend did not open a session" in str(e.value)
def test_session_transactions_keep_context(app, client, req_ctx):
client.get("/")
req = flask.request._get_current_object()
assert req is not None
with client.session_transaction():
assert req is flask.request._get_current_object()
def test_session_transaction_needs_cookies(app):
c = app.test_client(use_cookies=False)
with pytest.raises(TypeError, match="Cookies are disabled."):
with c.session_transaction():
pass
def test_test_client_context_binding(app, client):
app.testing = False
@app.route("/")
def index():
flask.g.value = 42
return "Hello World!"
@app.route("/other")
def other():
raise ZeroDivisionError
with client:
resp = client.get("/")
assert flask.g.value == 42
assert resp.data == b"Hello World!"
assert resp.status_code == 200
with client:
resp = client.get("/other")
assert not hasattr(flask.g, "value")
assert b"Internal Server Error" in resp.data
assert resp.status_code == 500
flask.g.value = 23
with pytest.raises(RuntimeError):
flask.g.value # noqa: B018
def test_reuse_client(client):
c = client
with c:
assert client.get("/").status_code == 404
with c:
assert client.get("/").status_code == 404
def test_full_url_request(app, client):
@app.route("/action", methods=["POST"])
def action():
return "x"
with client:
rv = client.post("http://domain.com/action?vodka=42", data={"gin": 43})
assert rv.status_code == 200
assert "gin" in flask.request.form
assert "vodka" in flask.request.args
def test_json_request_and_response(app, client):
@app.route("/echo", methods=["POST"])
def echo():
return jsonify(flask.request.get_json())
with client:
json_data = {"drink": {"gin": 1, "tonic": True}, "price": 10}
rv = client.post("/echo", json=json_data)
# Request should be in JSON
assert flask.request.is_json
assert flask.request.get_json() == json_data
# Response should be in JSON
assert rv.status_code == 200
assert rv.is_json
assert rv.get_json() == json_data
def test_client_json_no_app_context(app, client):
@app.route("/hello", methods=["POST"])
def hello():
return f"Hello, {flask.request.json['name']}!"
class Namespace:
count = 0
def add(self, app):
self.count += 1
ns = Namespace()
with appcontext_popped.connected_to(ns.add, app):
rv = client.post("/hello", json={"name": "Flask"})
assert rv.get_data(as_text=True) == "Hello, Flask!"
assert ns.count == 1
def test_subdomain():
app = flask.Flask(__name__, subdomain_matching=True)
app.config["SERVER_NAME"] = "example.com"
client = app.test_client()
@app.route("/", subdomain="<company_id>")
def view(company_id):
return company_id
with app.test_request_context():
url = flask.url_for("view", company_id="xxx")
with client:
response = client.get(url)
assert 200 == response.status_code
assert b"xxx" == response.data
def test_nosubdomain(app, client):
app.config["SERVER_NAME"] = "example.com"
@app.route("/<company_id>")
def view(company_id):
return company_id
with app.test_request_context():
url = flask.url_for("view", company_id="xxx")
with client:
response = client.get(url)
assert 200 == response.status_code
assert b"xxx" == response.data
def test_cli_runner_class(app):
runner = app.test_cli_runner()
assert isinstance(runner, FlaskCliRunner)
class SubRunner(FlaskCliRunner):
pass
app.test_cli_runner_class = SubRunner
runner = app.test_cli_runner()
assert isinstance(runner, SubRunner)
def test_cli_invoke(app):
@app.cli.command("hello")
def hello_command():
click.echo("Hello, World!")
runner = app.test_cli_runner()
# invoke with command name
result = runner.invoke(args=["hello"])
assert "Hello" in result.output
# invoke with command object
result = runner.invoke(hello_command)
assert "Hello" in result.output
def test_cli_custom_obj(app):
class NS:
called = False
def create_app():
NS.called = True
return app
@app.cli.command("hello")
def hello_command():
click.echo("Hello, World!")
script_info = ScriptInfo(create_app=create_app)
runner = app.test_cli_runner()
runner.invoke(hello_command, obj=script_info)
assert NS.called
def test_client_pop_all_preserved(app, req_ctx, client):
@app.route("/")
def index():
# stream_with_context pushes a third context, preserved by response
return flask.stream_with_context("hello")
# req_ctx fixture pushed an initial context
with client:
# request pushes a second request context, preserved by client
rv = client.get("/")
# close the response, releasing the context held by stream_with_context
rv.close()
# only req_ctx fixture should still be pushed
assert _cv_request.get(None) is req_ctx

View file

@ -1,295 +0,0 @@
import pytest
from werkzeug.exceptions import Forbidden
from werkzeug.exceptions import HTTPException
from werkzeug.exceptions import InternalServerError
from werkzeug.exceptions import NotFound
import flask
def test_error_handler_no_match(app, client):
class CustomException(Exception):
pass
@app.errorhandler(CustomException)
def custom_exception_handler(e):
assert isinstance(e, CustomException)
return "custom"
with pytest.raises(TypeError) as exc_info:
app.register_error_handler(CustomException(), None)
assert "CustomException() is an instance, not a class." in str(exc_info.value)
with pytest.raises(ValueError) as exc_info:
app.register_error_handler(list, None)
assert "'list' is not a subclass of Exception." in str(exc_info.value)
@app.errorhandler(500)
def handle_500(e):
assert isinstance(e, InternalServerError)
if e.original_exception is not None:
return f"wrapped {type(e.original_exception).__name__}"
return "direct"
with pytest.raises(ValueError) as exc_info:
app.register_error_handler(999, None)
assert "Use a subclass of HTTPException" in str(exc_info.value)
@app.route("/custom")
def custom_test():
raise CustomException()
@app.route("/keyerror")
def key_error():
raise KeyError()
@app.route("/abort")
def do_abort():
flask.abort(500)
app.testing = False
assert client.get("/custom").data == b"custom"
assert client.get("/keyerror").data == b"wrapped KeyError"
assert client.get("/abort").data == b"direct"
def test_error_handler_subclass(app):
class ParentException(Exception):
pass
class ChildExceptionUnregistered(ParentException):
pass
class ChildExceptionRegistered(ParentException):
pass
@app.errorhandler(ParentException)
def parent_exception_handler(e):
assert isinstance(e, ParentException)
return "parent"
@app.errorhandler(ChildExceptionRegistered)
def child_exception_handler(e):
assert isinstance(e, ChildExceptionRegistered)
return "child-registered"
@app.route("/parent")
def parent_test():
raise ParentException()
@app.route("/child-unregistered")
def unregistered_test():
raise ChildExceptionUnregistered()
@app.route("/child-registered")
def registered_test():
raise ChildExceptionRegistered()
c = app.test_client()
assert c.get("/parent").data == b"parent"
assert c.get("/child-unregistered").data == b"parent"
assert c.get("/child-registered").data == b"child-registered"
def test_error_handler_http_subclass(app):
class ForbiddenSubclassRegistered(Forbidden):
pass
class ForbiddenSubclassUnregistered(Forbidden):
pass
@app.errorhandler(403)
def code_exception_handler(e):
assert isinstance(e, Forbidden)
return "forbidden"
@app.errorhandler(ForbiddenSubclassRegistered)
def subclass_exception_handler(e):
assert isinstance(e, ForbiddenSubclassRegistered)
return "forbidden-registered"
@app.route("/forbidden")
def forbidden_test():
raise Forbidden()
@app.route("/forbidden-registered")
def registered_test():
raise ForbiddenSubclassRegistered()
@app.route("/forbidden-unregistered")
def unregistered_test():
raise ForbiddenSubclassUnregistered()
c = app.test_client()
assert c.get("/forbidden").data == b"forbidden"
assert c.get("/forbidden-unregistered").data == b"forbidden"
assert c.get("/forbidden-registered").data == b"forbidden-registered"
def test_error_handler_blueprint(app):
bp = flask.Blueprint("bp", __name__)
@bp.errorhandler(500)
def bp_exception_handler(e):
return "bp-error"
@bp.route("/error")
def bp_test():
raise InternalServerError()
@app.errorhandler(500)
def app_exception_handler(e):
return "app-error"
@app.route("/error")
def app_test():
raise InternalServerError()
app.register_blueprint(bp, url_prefix="/bp")
c = app.test_client()
assert c.get("/error").data == b"app-error"
assert c.get("/bp/error").data == b"bp-error"
def test_default_error_handler():
bp = flask.Blueprint("bp", __name__)
@bp.errorhandler(HTTPException)
def bp_exception_handler(e):
assert isinstance(e, HTTPException)
assert isinstance(e, NotFound)
return "bp-default"
@bp.errorhandler(Forbidden)
def bp_forbidden_handler(e):
assert isinstance(e, Forbidden)
return "bp-forbidden"
@bp.route("/undefined")
def bp_registered_test():
raise NotFound()
@bp.route("/forbidden")
def bp_forbidden_test():
raise Forbidden()
app = flask.Flask(__name__)
@app.errorhandler(HTTPException)
def catchall_exception_handler(e):
assert isinstance(e, HTTPException)
assert isinstance(e, NotFound)
return "default"
@app.errorhandler(Forbidden)
def catchall_forbidden_handler(e):
assert isinstance(e, Forbidden)
return "forbidden"
@app.route("/forbidden")
def forbidden():
raise Forbidden()
@app.route("/slash/")
def slash():
return "slash"
app.register_blueprint(bp, url_prefix="/bp")
c = app.test_client()
assert c.get("/bp/undefined").data == b"bp-default"
assert c.get("/bp/forbidden").data == b"bp-forbidden"
assert c.get("/undefined").data == b"default"
assert c.get("/forbidden").data == b"forbidden"
# Don't handle RequestRedirect raised when adding slash.
assert c.get("/slash", follow_redirects=True).data == b"slash"
class TestGenericHandlers:
"""Test how very generic handlers are dispatched to."""
class Custom(Exception):
pass
@pytest.fixture()
def app(self, app):
@app.route("/custom")
def do_custom():
raise self.Custom()
@app.route("/error")
def do_error():
raise KeyError()
@app.route("/abort")
def do_abort():
flask.abort(500)
@app.route("/raise")
def do_raise():
raise InternalServerError()
app.config["PROPAGATE_EXCEPTIONS"] = False
return app
def report_error(self, e):
original = getattr(e, "original_exception", None)
if original is not None:
return f"wrapped {type(original).__name__}"
return f"direct {type(e).__name__}"
@pytest.mark.parametrize("to_handle", (InternalServerError, 500))
def test_handle_class_or_code(self, app, client, to_handle):
"""``InternalServerError`` and ``500`` are aliases, they should
have the same behavior. Both should only receive
``InternalServerError``, which might wrap another error.
"""
@app.errorhandler(to_handle)
def handle_500(e):
assert isinstance(e, InternalServerError)
return self.report_error(e)
assert client.get("/custom").data == b"wrapped Custom"
assert client.get("/error").data == b"wrapped KeyError"
assert client.get("/abort").data == b"direct InternalServerError"
assert client.get("/raise").data == b"direct InternalServerError"
def test_handle_generic_http(self, app, client):
"""``HTTPException`` should only receive ``HTTPException``
subclasses. It will receive ``404`` routing exceptions.
"""
@app.errorhandler(HTTPException)
def handle_http(e):
assert isinstance(e, HTTPException)
return str(e.code)
assert client.get("/error").data == b"500"
assert client.get("/abort").data == b"500"
assert client.get("/not-found").data == b"404"
def test_handle_generic(self, app, client):
"""Generic ``Exception`` will handle all exceptions directly,
including ``HTTPExceptions``.
"""
@app.errorhandler(Exception)
def handle_exception(e):
return self.report_error(e)
assert client.get("/custom").data == b"direct Custom"
assert client.get("/error").data == b"direct KeyError"
assert client.get("/abort").data == b"direct InternalServerError"
assert client.get("/not-found").data == b"direct NotFound"

View file

@ -1,260 +0,0 @@
import pytest
from werkzeug.http import parse_set_header
import flask.views
def common_test(app):
c = app.test_client()
assert c.get("/").data == b"GET"
assert c.post("/").data == b"POST"
assert c.put("/").status_code == 405
meths = parse_set_header(c.open("/", method="OPTIONS").headers["Allow"])
assert sorted(meths) == ["GET", "HEAD", "OPTIONS", "POST"]
def test_basic_view(app):
class Index(flask.views.View):
methods = ["GET", "POST"]
def dispatch_request(self):
return flask.request.method
app.add_url_rule("/", view_func=Index.as_view("index"))
common_test(app)
def test_method_based_view(app):
class Index(flask.views.MethodView):
def get(self):
return "GET"
def post(self):
return "POST"
app.add_url_rule("/", view_func=Index.as_view("index"))
common_test(app)
def test_view_patching(app):
class Index(flask.views.MethodView):
def get(self):
raise ZeroDivisionError
def post(self):
raise ZeroDivisionError
class Other(Index):
def get(self):
return "GET"
def post(self):
return "POST"
view = Index.as_view("index")
view.view_class = Other
app.add_url_rule("/", view_func=view)
common_test(app)
def test_view_inheritance(app, client):
class Index(flask.views.MethodView):
def get(self):
return "GET"
def post(self):
return "POST"
class BetterIndex(Index):
def delete(self):
return "DELETE"
app.add_url_rule("/", view_func=BetterIndex.as_view("index"))
meths = parse_set_header(client.open("/", method="OPTIONS").headers["Allow"])
assert sorted(meths) == ["DELETE", "GET", "HEAD", "OPTIONS", "POST"]
def test_view_decorators(app, client):
def add_x_parachute(f):
def new_function(*args, **kwargs):
resp = flask.make_response(f(*args, **kwargs))
resp.headers["X-Parachute"] = "awesome"
return resp
return new_function
class Index(flask.views.View):
decorators = [add_x_parachute]
def dispatch_request(self):
return "Awesome"
app.add_url_rule("/", view_func=Index.as_view("index"))
rv = client.get("/")
assert rv.headers["X-Parachute"] == "awesome"
assert rv.data == b"Awesome"
def test_view_provide_automatic_options_attr():
app = flask.Flask(__name__)
class Index1(flask.views.View):
provide_automatic_options = False
def dispatch_request(self):
return "Hello World!"
app.add_url_rule("/", view_func=Index1.as_view("index"))
c = app.test_client()
rv = c.open("/", method="OPTIONS")
assert rv.status_code == 405
app = flask.Flask(__name__)
class Index2(flask.views.View):
methods = ["OPTIONS"]
provide_automatic_options = True
def dispatch_request(self):
return "Hello World!"
app.add_url_rule("/", view_func=Index2.as_view("index"))
c = app.test_client()
rv = c.open("/", method="OPTIONS")
assert sorted(rv.allow) == ["OPTIONS"]
app = flask.Flask(__name__)
class Index3(flask.views.View):
def dispatch_request(self):
return "Hello World!"
app.add_url_rule("/", view_func=Index3.as_view("index"))
c = app.test_client()
rv = c.open("/", method="OPTIONS")
assert "OPTIONS" in rv.allow
def test_implicit_head(app, client):
class Index(flask.views.MethodView):
def get(self):
return flask.Response("Blub", headers={"X-Method": flask.request.method})
app.add_url_rule("/", view_func=Index.as_view("index"))
rv = client.get("/")
assert rv.data == b"Blub"
assert rv.headers["X-Method"] == "GET"
rv = client.head("/")
assert rv.data == b""
assert rv.headers["X-Method"] == "HEAD"
def test_explicit_head(app, client):
class Index(flask.views.MethodView):
def get(self):
return "GET"
def head(self):
return flask.Response("", headers={"X-Method": "HEAD"})
app.add_url_rule("/", view_func=Index.as_view("index"))
rv = client.get("/")
assert rv.data == b"GET"
rv = client.head("/")
assert rv.data == b""
assert rv.headers["X-Method"] == "HEAD"
def test_endpoint_override(app):
app.debug = True
class Index(flask.views.View):
methods = ["GET", "POST"]
def dispatch_request(self):
return flask.request.method
app.add_url_rule("/", view_func=Index.as_view("index"))
with pytest.raises(AssertionError):
app.add_url_rule("/", view_func=Index.as_view("index"))
# But these tests should still pass. We just log a warning.
common_test(app)
def test_methods_var_inheritance(app, client):
class BaseView(flask.views.MethodView):
methods = ["GET", "PROPFIND"]
class ChildView(BaseView):
def get(self):
return "GET"
def propfind(self):
return "PROPFIND"
app.add_url_rule("/", view_func=ChildView.as_view("index"))
assert client.get("/").data == b"GET"
assert client.open("/", method="PROPFIND").data == b"PROPFIND"
assert ChildView.methods == {"PROPFIND", "GET"}
def test_multiple_inheritance(app, client):
class GetView(flask.views.MethodView):
def get(self):
return "GET"
class DeleteView(flask.views.MethodView):
def delete(self):
return "DELETE"
class GetDeleteView(GetView, DeleteView):
pass
app.add_url_rule("/", view_func=GetDeleteView.as_view("index"))
assert client.get("/").data == b"GET"
assert client.delete("/").data == b"DELETE"
assert sorted(GetDeleteView.methods) == ["DELETE", "GET"]
def test_remove_method_from_parent(app, client):
class GetView(flask.views.MethodView):
def get(self):
return "GET"
class OtherView(flask.views.MethodView):
def post(self):
return "POST"
class View(GetView, OtherView):
methods = ["GET"]
app.add_url_rule("/", view_func=View.as_view("index"))
assert client.get("/").data == b"GET"
assert client.post("/").status_code == 405
assert sorted(View.methods) == ["GET"]
def test_init_once(app, client):
n = 0
class CountInit(flask.views.View):
init_every_request = False
def __init__(self):
nonlocal n
n += 1
def dispatch_request(self):
return str(n)
app.add_url_rule("/", view_func=CountInit.as_view("index"))
assert client.get("/").data == b"1"
assert client.get("/").data == b"1"

View file

@ -1,32 +0,0 @@
from __future__ import annotations
from flask import Flask
from flask import Response
app = Flask(__name__)
@app.after_request
def after_sync(response: Response) -> Response:
return Response()
@app.after_request
async def after_async(response: Response) -> Response:
return Response()
@app.before_request
def before_sync() -> None: ...
@app.before_request
async def before_async() -> None: ...
@app.teardown_appcontext
def teardown_sync(exc: BaseException | None) -> None: ...
@app.teardown_appcontext
async def teardown_async(exc: BaseException | None) -> None: ...

View file

@ -1,33 +0,0 @@
from __future__ import annotations
from http import HTTPStatus
from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import NotFound
from flask import Flask
app = Flask(__name__)
@app.errorhandler(400)
@app.errorhandler(HTTPStatus.BAD_REQUEST)
@app.errorhandler(BadRequest)
def handle_400(e: BadRequest) -> str:
return ""
@app.errorhandler(ValueError)
def handle_custom(e: ValueError) -> str:
return ""
@app.errorhandler(ValueError)
def handle_accept_base(e: Exception) -> str:
return ""
@app.errorhandler(BadRequest)
@app.errorhandler(404)
def handle_multiple(e: BadRequest | NotFound) -> str:
return ""

View file

@ -1,112 +0,0 @@
from __future__ import annotations
import typing as t
from http import HTTPStatus
from flask import Flask
from flask import jsonify
from flask import stream_template
from flask.templating import render_template
from flask.views import View
from flask.wrappers import Response
app = Flask(__name__)
@app.route("/str")
def hello_str() -> str:
return "<p>Hello, World!</p>"
@app.route("/bytes")
def hello_bytes() -> bytes:
return b"<p>Hello, World!</p>"
@app.route("/json")
def hello_json() -> Response:
return jsonify("Hello, World!")
@app.route("/json/dict")
def hello_json_dict() -> dict[str, t.Any]:
return {"response": "Hello, World!"}
@app.route("/json/dict")
def hello_json_list() -> list[t.Any]:
return [{"message": "Hello"}, {"message": "World"}]
class StatusJSON(t.TypedDict):
status: str
@app.route("/typed-dict")
def typed_dict() -> StatusJSON:
return {"status": "ok"}
@app.route("/generator")
def hello_generator() -> t.Generator[str, None, None]:
def show() -> t.Generator[str, None, None]:
for x in range(100):
yield f"data:{x}\n\n"
return show()
@app.route("/generator-expression")
def hello_generator_expression() -> t.Iterator[bytes]:
return (f"data:{x}\n\n".encode() for x in range(100))
@app.route("/iterator")
def hello_iterator() -> t.Iterator[str]:
return iter([f"data:{x}\n\n" for x in range(100)])
@app.route("/status")
@app.route("/status/<int:code>")
def tuple_status(code: int = 200) -> tuple[str, int]:
return "hello", code
@app.route("/status-enum")
def tuple_status_enum() -> tuple[str, int]:
return "hello", HTTPStatus.OK
@app.route("/headers")
def tuple_headers() -> tuple[str, dict[str, str]]:
return "Hello, World!", {"Content-Type": "text/plain"}
@app.route("/template")
@app.route("/template/<name>")
def return_template(name: str | None = None) -> str:
return render_template("index.html", name=name)
@app.route("/template")
def return_template_stream() -> t.Iterator[str]:
return stream_template("index.html", name="Hello")
@app.route("/async")
async def async_route() -> str:
return "Hello"
class RenderTemplateView(View):
def __init__(self: RenderTemplateView, template_name: str) -> None:
self.template_name = template_name
def dispatch_request(self: RenderTemplateView) -> str:
return render_template(self.template_name)
app.add_url_rule(
"/about",
view_func=RenderTemplateView.as_view("about_page", template_name="about.html"),
)