diff --git "a/Code.txt" "b/Code.txt" new file mode 100644--- /dev/null +++ "b/Code.txt" @@ -0,0 +1,17855 @@ +Filepath: githubCode\docs\conf.py: + +import packaging.version +from pallets_sphinx_themes import get_version +from pallets_sphinx_themes import ProjectLink + +# Project -------------------------------------------------------------- + +project = "Flask" +copyright = "2010 Pallets" +author = "Pallets" +release, version = get_version("Flask") + +# General -------------------------------------------------------------- + +master_doc = "index" +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinxcontrib.log_cabinet", + "pallets_sphinx_themes", + "sphinx_issues", + "sphinx_tabs.tabs", +] +autodoc_typehints = "description" +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "werkzeug": ("https://werkzeug.palletsprojects.com/", None), + "click": ("https://click.palletsprojects.com/", None), + "jinja": ("https://jinja.palletsprojects.com/", None), + "itsdangerous": ("https://itsdangerous.palletsprojects.com/", None), + "sqlalchemy": ("https://docs.sqlalchemy.org/", None), + "wtforms": ("https://wtforms.readthedocs.io/", None), + "blinker": ("https://blinker.readthedocs.io/", None), +} +issues_github_path = "pallets/flask" + +# HTML ----------------------------------------------------------------- + +html_theme = "flask" +html_theme_options = {"index_sidebar_logo": False} +html_context = { + "project_links": [ + ProjectLink("Donate", "https://palletsprojects.com/donate"), + ProjectLink("PyPI Releases", "https://pypi.org/project/Flask/"), + ProjectLink("Source Code", "https://github.com/pallets/flask/"), + ProjectLink("Issue Tracker", "https://github.com/pallets/flask/issues/"), + ProjectLink("Chat", "https://discord.gg/pallets"), + ] +} +html_sidebars = { + "index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"], + "**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"], +} +singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]} +html_static_path = ["_static"] +html_favicon = "_static/shortcut-icon.png" +html_logo = "_static/flask-vertical.png" +html_title = f"Flask Documentation ({version})" +html_show_sourcelink = False + +# LaTeX ---------------------------------------------------------------- + +latex_documents = [(master_doc, f"Flask-{version}.tex", html_title, author, "manual")] + +# Local Extensions ----------------------------------------------------- + + +def github_link(name, rawtext, text, lineno, inliner, options=None, content=None): + app = inliner.document.settings.env.app + release = app.config.release + base_url = "https://github.com/pallets/flask/tree/" + + if text.endswith(">"): + words, text = text[:-1].rsplit("<", 1) + words = words.strip() + else: + words = None + + if packaging.version.parse(release).is_devrelease: + url = f"{base_url}main/{text}" + else: + url = f"{base_url}{release}/{text}" + + if words is None: + words = url + + from docutils.nodes import reference + from docutils.parsers.rst.roles import set_classes + + options = options or {} + set_classes(options) + node = reference(rawtext, words, refuri=url, **options) + return [node], [] + + +def setup(app): + app.add_role("gh", github_link) + + +Filepath: githubCode\tests\conftest.py: + +import os +import pkgutil +import sys + +import pytest +from _pytest import monkeypatch + +from flask import Flask +from flask.globals import request_ctx + + +@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) + + +@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 + + +@pytest.fixture +def app_ctx(app): + with app.app_context() as ctx: + yield ctx + + +@pytest.fixture +def req_ctx(app): + with app.test_request_context() as ctx: + yield ctx + + +@pytest.fixture +def client(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) + + +@pytest.fixture(autouse=True) +def leak_detector(): + yield + + # 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() + + 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) + + +@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 + + +Filepath: githubCode\tests\test_appctx.py: + +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) == "" + + +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 + + +Filepath: githubCode\tests\test_async.py: + +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 + + +Filepath: githubCode\tests\test_basic.py: + +import gc +import re +import uuid +import warnings +import weakref +from datetime import datetime +from datetime import timezone +from platform import python_implementation + +import pytest +import werkzeug.serving +from markupsafe import Markup +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import Forbidden +from werkzeug.exceptions import NotFound +from werkzeug.http import parse_date +from werkzeug.routing import BuildError +from werkzeug.routing import RequestRedirect + +import flask + +require_cpython_gc = pytest.mark.skipif( + python_implementation() != "CPython", + reason="Requires CPython GC behavior", +) + + +def test_options_work(app, client): + @app.route("/", methods=["GET", "POST"]) + def index(): + return "Hello World" + + rv = client.open("/", method="OPTIONS") + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"] + assert rv.data == b"" + + +def test_options_on_multiple_rules(app, client): + @app.route("/", methods=["GET", "POST"]) + def index(): + return "Hello World" + + @app.route("/", methods=["PUT"]) + def index_put(): + return "Aha!" + + rv = client.open("/", method="OPTIONS") + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST", "PUT"] + + +@pytest.mark.parametrize("method", ["get", "post", "put", "delete", "patch"]) +def test_method_route(app, client, method): + method_route = getattr(app, method) + client_method = getattr(client, method) + + @method_route("/") + def hello(): + return "Hello" + + assert client_method("/").data == b"Hello" + + +def test_method_route_no_methods(app): + with pytest.raises(TypeError): + app.get("/", methods=["GET", "POST"]) + + +def test_provide_automatic_options_attr(): + app = flask.Flask(__name__) + + def index(): + return "Hello World!" + + index.provide_automatic_options = False + app.route("/")(index) + rv = app.test_client().open("/", method="OPTIONS") + assert rv.status_code == 405 + + app = flask.Flask(__name__) + + def index2(): + return "Hello World!" + + index2.provide_automatic_options = True + app.route("/", methods=["OPTIONS"])(index2) + rv = app.test_client().open("/", method="OPTIONS") + assert sorted(rv.allow) == ["OPTIONS"] + + +def test_provide_automatic_options_kwarg(app, client): + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule("/", view_func=index, provide_automatic_options=False) + app.add_url_rule( + "/more", + view_func=more, + methods=["GET", "POST"], + provide_automatic_options=False, + ) + assert client.get("/").data == b"GET" + + rv = client.post("/") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD"] + + rv = client.open("/", method="OPTIONS") + assert rv.status_code == 405 + + rv = client.head("/") + assert rv.status_code == 200 + assert not rv.data # head truncates + assert client.post("/more").data == b"POST" + assert client.get("/more").data == b"GET" + + rv = client.delete("/more") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "POST"] + + rv = client.open("/more", method="OPTIONS") + assert rv.status_code == 405 + + +def test_request_dispatching(app, client): + @app.route("/") + def index(): + return flask.request.method + + @app.route("/more", methods=["GET", "POST"]) + def more(): + return flask.request.method + + assert client.get("/").data == b"GET" + rv = client.post("/") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS"] + rv = client.head("/") + assert rv.status_code == 200 + assert not rv.data # head truncates + assert client.post("/more").data == b"POST" + assert client.get("/more").data == b"GET" + rv = client.delete("/more") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"] + + +def test_disallow_string_for_allowed_methods(app): + with pytest.raises(TypeError): + app.add_url_rule("/", methods="GET POST", endpoint="test") + + +def test_url_mapping(app, client): + random_uuid4 = "7eb41166-9ebf-4d26-b771-ea3f54f8b383" + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + def options(): + return random_uuid4 + + app.add_url_rule("/", "index", index) + app.add_url_rule("/more", "more", more, methods=["GET", "POST"]) + + # Issue 1288: Test that automatic options are not added + # when non-uppercase 'options' in methods + app.add_url_rule("/options", "options", options, methods=["options"]) + + assert client.get("/").data == b"GET" + rv = client.post("/") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS"] + rv = client.head("/") + assert rv.status_code == 200 + assert not rv.data # head truncates + assert client.post("/more").data == b"POST" + assert client.get("/more").data == b"GET" + rv = client.delete("/more") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"] + rv = client.open("/options", method="OPTIONS") + assert rv.status_code == 200 + assert random_uuid4 in rv.data.decode("utf-8") + + +def test_werkzeug_routing(app, client): + from werkzeug.routing import Rule + from werkzeug.routing import Submount + + app.url_map.add( + Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")]) + ) + + def bar(): + return "bar" + + def index(): + return "index" + + app.view_functions["bar"] = bar + app.view_functions["index"] = index + + assert client.get("/foo/").data == b"index" + assert client.get("/foo/bar").data == b"bar" + + +def test_endpoint_decorator(app, client): + from werkzeug.routing import Rule + from werkzeug.routing import Submount + + app.url_map.add( + Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")]) + ) + + @app.endpoint("bar") + def bar(): + return "bar" + + @app.endpoint("index") + def index(): + return "index" + + assert client.get("/foo/").data == b"index" + assert client.get("/foo/bar").data == b"bar" + + +def test_session(app, client): + @app.route("/set", methods=["POST"]) + def set(): + assert not flask.session.accessed + assert not flask.session.modified + flask.session["value"] = flask.request.form["value"] + assert flask.session.accessed + assert flask.session.modified + return "value set" + + @app.route("/get") + def get(): + assert not flask.session.accessed + assert not flask.session.modified + v = flask.session.get("value", "None") + assert flask.session.accessed + assert not flask.session.modified + return v + + assert client.post("/set", data={"value": "42"}).data == b"value set" + assert client.get("/get").data == b"42" + + +def test_session_path(app, client): + app.config.update(APPLICATION_ROOT="/foo") + + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + rv = client.get("/", "http://example.com:8080/foo") + assert "path=/foo" in rv.headers["set-cookie"].lower() + + +def test_session_using_application_root(app, client): + class PrefixPathMiddleware: + def __init__(self, app, prefix): + self.app = app + self.prefix = prefix + + def __call__(self, environ, start_response): + environ["SCRIPT_NAME"] = self.prefix + return self.app(environ, start_response) + + app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, "/bar") + app.config.update(APPLICATION_ROOT="/bar") + + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + rv = client.get("/", "http://example.com:8080/") + assert "path=/bar" in rv.headers["set-cookie"].lower() + + +def test_session_using_session_settings(app, client): + app.config.update( + SERVER_NAME="www.example.com:8080", + APPLICATION_ROOT="/test", + SESSION_COOKIE_DOMAIN=".example.com", + SESSION_COOKIE_HTTPONLY=False, + SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_SAMESITE="Lax", + SESSION_COOKIE_PATH="/", + ) + + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + @app.route("/clear") + def clear(): + flask.session.pop("testing", None) + return "Goodbye World" + + rv = client.get("/", "http://www.example.com:8080/test/") + cookie = rv.headers["set-cookie"].lower() + # or condition for Werkzeug < 2.3 + assert "domain=example.com" in cookie or "domain=.example.com" in cookie + assert "path=/" in cookie + assert "secure" in cookie + assert "httponly" not in cookie + assert "samesite" in cookie + + rv = client.get("/clear", "http://www.example.com:8080/test/") + cookie = rv.headers["set-cookie"].lower() + assert "session=;" in cookie + # or condition for Werkzeug < 2.3 + assert "domain=example.com" in cookie or "domain=.example.com" in cookie + assert "path=/" in cookie + assert "secure" in cookie + assert "samesite" in cookie + + +def test_session_using_samesite_attribute(app, client): + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + app.config.update(SESSION_COOKIE_SAMESITE="invalid") + + with pytest.raises(ValueError): + client.get("/") + + app.config.update(SESSION_COOKIE_SAMESITE=None) + rv = client.get("/") + cookie = rv.headers["set-cookie"].lower() + assert "samesite" not in cookie + + app.config.update(SESSION_COOKIE_SAMESITE="Strict") + rv = client.get("/") + cookie = rv.headers["set-cookie"].lower() + assert "samesite=strict" in cookie + + app.config.update(SESSION_COOKIE_SAMESITE="Lax") + rv = client.get("/") + cookie = rv.headers["set-cookie"].lower() + assert "samesite=lax" in cookie + + +def test_missing_session(app): + app.secret_key = None + + def expect_exception(f, *args, **kwargs): + e = pytest.raises(RuntimeError, f, *args, **kwargs) + assert e.value.args and "session is unavailable" in e.value.args[0] + + with app.test_request_context(): + assert flask.session.get("missing_key") is None + expect_exception(flask.session.__setitem__, "foo", 42) + expect_exception(flask.session.pop, "foo") + + +def test_session_expiration(app, client): + permanent = True + + @app.route("/") + def index(): + flask.session["test"] = 42 + flask.session.permanent = permanent + return "" + + @app.route("/test") + def test(): + return str(flask.session.permanent) + + rv = client.get("/") + assert "set-cookie" in rv.headers + match = re.search(r"(?i)\bexpires=([^;]+)", rv.headers["set-cookie"]) + expires = parse_date(match.group()) + expected = datetime.now(timezone.utc) + app.permanent_session_lifetime + assert expires.year == expected.year + assert expires.month == expected.month + assert expires.day == expected.day + + rv = client.get("/test") + assert rv.data == b"True" + + permanent = False + rv = client.get("/") + assert "set-cookie" in rv.headers + match = re.search(r"\bexpires=([^;]+)", rv.headers["set-cookie"]) + assert match is None + + +def test_session_stored_last(app, client): + @app.after_request + def modify_session(response): + flask.session["foo"] = 42 + return response + + @app.route("/") + def dump_session_contents(): + return repr(flask.session.get("foo")) + + assert client.get("/").data == b"None" + assert client.get("/").data == b"42" + + +def test_session_special_types(app, client): + now = datetime.now(timezone.utc).replace(microsecond=0) + the_uuid = uuid.uuid4() + + @app.route("/") + def dump_session_contents(): + flask.session["t"] = (1, 2, 3) + flask.session["b"] = b"\xff" + flask.session["m"] = Markup("") + flask.session["u"] = the_uuid + flask.session["d"] = now + flask.session["t_tag"] = {" t": "not-a-tuple"} + flask.session["di_t_tag"] = {" t__": "not-a-tuple"} + flask.session["di_tag"] = {" di": "not-a-dict"} + return "", 204 + + with client: + client.get("/") + s = flask.session + assert s["t"] == (1, 2, 3) + assert type(s["b"]) is bytes # noqa: E721 + assert s["b"] == b"\xff" + assert type(s["m"]) is Markup # noqa: E721 + assert s["m"] == Markup("") + assert s["u"] == the_uuid + assert s["d"] == now + assert s["t_tag"] == {" t": "not-a-tuple"} + assert s["di_t_tag"] == {" t__": "not-a-tuple"} + assert s["di_tag"] == {" di": "not-a-dict"} + + +def test_session_cookie_setting(app): + is_permanent = True + + @app.route("/bump") + def bump(): + rv = flask.session["foo"] = flask.session.get("foo", 0) + 1 + flask.session.permanent = is_permanent + return str(rv) + + @app.route("/read") + def read(): + return str(flask.session.get("foo", 0)) + + def run_test(expect_header): + with app.test_client() as c: + assert c.get("/bump").data == b"1" + assert c.get("/bump").data == b"2" + assert c.get("/bump").data == b"3" + + rv = c.get("/read") + set_cookie = rv.headers.get("set-cookie") + assert (set_cookie is not None) == expect_header + assert rv.data == b"3" + + is_permanent = True + app.config["SESSION_REFRESH_EACH_REQUEST"] = True + run_test(expect_header=True) + + is_permanent = True + app.config["SESSION_REFRESH_EACH_REQUEST"] = False + run_test(expect_header=False) + + is_permanent = False + app.config["SESSION_REFRESH_EACH_REQUEST"] = True + run_test(expect_header=False) + + is_permanent = False + app.config["SESSION_REFRESH_EACH_REQUEST"] = False + run_test(expect_header=False) + + +def test_session_vary_cookie(app, client): + @app.route("/set") + def set_session(): + flask.session["test"] = "test" + return "" + + @app.route("/get") + def get(): + return flask.session.get("test") + + @app.route("/getitem") + def getitem(): + return flask.session["test"] + + @app.route("/setdefault") + def setdefault(): + return flask.session.setdefault("test", "default") + + @app.route("/clear") + def clear(): + flask.session.clear() + return "" + + @app.route("/vary-cookie-header-set") + def vary_cookie_header_set(): + response = flask.Response() + response.vary.add("Cookie") + flask.session["test"] = "test" + return response + + @app.route("/vary-header-set") + def vary_header_set(): + response = flask.Response() + response.vary.update(("Accept-Encoding", "Accept-Language")) + flask.session["test"] = "test" + return response + + @app.route("/no-vary-header") + def no_vary_header(): + return "" + + def expect(path, header_value="Cookie"): + rv = client.get(path) + + if header_value: + # The 'Vary' key should exist in the headers only once. + assert len(rv.headers.get_all("Vary")) == 1 + assert rv.headers["Vary"] == header_value + else: + assert "Vary" not in rv.headers + + expect("/set") + expect("/get") + expect("/getitem") + expect("/setdefault") + expect("/clear") + expect("/vary-cookie-header-set") + expect("/vary-header-set", "Accept-Encoding, Accept-Language, Cookie") + expect("/no-vary-header", None) + + +def test_session_refresh_vary(app, client): + @app.get("/login") + def login(): + flask.session["user_id"] = 1 + flask.session.permanent = True + return "" + + @app.get("/ignored") + def ignored(): + return "" + + rv = client.get("/login") + assert rv.headers["Vary"] == "Cookie" + rv = client.get("/ignored") + assert rv.headers["Vary"] == "Cookie" + + +def test_flashes(app, req_ctx): + assert not flask.session.modified + flask.flash("Zap") + flask.session.modified = False + flask.flash("Zip") + assert flask.session.modified + assert list(flask.get_flashed_messages()) == ["Zap", "Zip"] + + +def test_extended_flashing(app): + # Be sure app.testing=True below, else tests can fail silently. + # + # Specifically, if app.testing is not set to True, the AssertionErrors + # in the view functions will cause a 500 response to the test client + # instead of propagating exceptions. + + @app.route("/") + def index(): + flask.flash("Hello World") + flask.flash("Hello World", "error") + flask.flash(Markup("Testing"), "warning") + return "" + + @app.route("/test/") + def test(): + messages = flask.get_flashed_messages() + assert list(messages) == [ + "Hello World", + "Hello World", + Markup("Testing"), + ] + return "" + + @app.route("/test_with_categories/") + def test_with_categories(): + messages = flask.get_flashed_messages(with_categories=True) + assert len(messages) == 3 + assert list(messages) == [ + ("message", "Hello World"), + ("error", "Hello World"), + ("warning", Markup("Testing")), + ] + return "" + + @app.route("/test_filter/") + def test_filter(): + messages = flask.get_flashed_messages( + category_filter=["message"], with_categories=True + ) + assert list(messages) == [("message", "Hello World")] + return "" + + @app.route("/test_filters/") + def test_filters(): + messages = flask.get_flashed_messages( + category_filter=["message", "warning"], with_categories=True + ) + assert list(messages) == [ + ("message", "Hello World"), + ("warning", Markup("Testing")), + ] + return "" + + @app.route("/test_filters_without_returning_categories/") + def test_filters2(): + messages = flask.get_flashed_messages(category_filter=["message", "warning"]) + assert len(messages) == 2 + assert messages[0] == "Hello World" + assert messages[1] == Markup("Testing") + return "" + + # Create new test client on each test to clean flashed messages. + + client = app.test_client() + client.get("/") + client.get("/test_with_categories/") + + client = app.test_client() + client.get("/") + client.get("/test_filter/") + + client = app.test_client() + client.get("/") + client.get("/test_filters/") + + client = app.test_client() + client.get("/") + client.get("/test_filters_without_returning_categories/") + + +def test_request_processing(app, client): + evts = [] + + @app.before_request + def before_request(): + evts.append("before") + + @app.after_request + def after_request(response): + response.data += b"|after" + evts.append("after") + return response + + @app.route("/") + def index(): + assert "before" in evts + assert "after" not in evts + return "request" + + assert "after" not in evts + rv = client.get("/").data + assert "after" in evts + assert rv == b"request|after" + + +def test_request_preprocessing_early_return(app, client): + evts = [] + + @app.before_request + def before_request1(): + evts.append(1) + + @app.before_request + def before_request2(): + evts.append(2) + return "hello" + + @app.before_request + def before_request3(): + evts.append(3) + return "bye" + + @app.route("/") + def index(): + evts.append("index") + return "damnit" + + rv = client.get("/").data.strip() + assert rv == b"hello" + assert evts == [1, 2] + + +def test_after_request_processing(app, client): + @app.route("/") + def index(): + @flask.after_this_request + def foo(response): + response.headers["X-Foo"] = "a header" + return response + + return "Test" + + resp = client.get("/") + assert resp.status_code == 200 + assert resp.headers["X-Foo"] == "a header" + + +def test_teardown_request_handler(app, client): + called = [] + + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + + @app.route("/") + def root(): + return "Response" + + rv = client.get("/") + assert rv.status_code == 200 + assert b"Response" in rv.data + assert len(called) == 1 + + +def test_teardown_request_handler_debug_mode(app, client): + called = [] + + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + + @app.route("/") + def root(): + return "Response" + + rv = client.get("/") + assert rv.status_code == 200 + assert b"Response" in rv.data + assert len(called) == 1 + + +def test_teardown_request_handler_error(app, client): + called = [] + app.testing = False + + @app.teardown_request + def teardown_request1(exc): + assert type(exc) is ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError() + except Exception: + pass + + @app.teardown_request + def teardown_request2(exc): + assert type(exc) is ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError() + except Exception: + pass + + @app.route("/") + def fails(): + raise ZeroDivisionError + + rv = client.get("/") + assert rv.status_code == 500 + assert b"Internal Server Error" in rv.data + assert len(called) == 2 + + +def test_before_after_request_order(app, client): + called = [] + + @app.before_request + def before1(): + called.append(1) + + @app.before_request + def before2(): + called.append(2) + + @app.after_request + def after1(response): + called.append(4) + return response + + @app.after_request + def after2(response): + called.append(3) + return response + + @app.teardown_request + def finish1(exc): + called.append(6) + + @app.teardown_request + def finish2(exc): + called.append(5) + + @app.route("/") + def index(): + return "42" + + rv = client.get("/") + assert rv.data == b"42" + assert called == [1, 2, 3, 4, 5, 6] + + +def test_error_handling(app, client): + app.testing = False + + @app.errorhandler(404) + def not_found(e): + return "not found", 404 + + @app.errorhandler(500) + def internal_server_error(e): + return "internal server error", 500 + + @app.errorhandler(Forbidden) + def forbidden(e): + return "forbidden", 403 + + @app.route("/") + def index(): + flask.abort(404) + + @app.route("/error") + def error(): + raise ZeroDivisionError + + @app.route("/forbidden") + def error2(): + flask.abort(403) + + rv = client.get("/") + assert rv.status_code == 404 + assert rv.data == b"not found" + rv = client.get("/error") + assert rv.status_code == 500 + assert b"internal server error" == rv.data + rv = client.get("/forbidden") + assert rv.status_code == 403 + assert b"forbidden" == rv.data + + +def test_error_handling_processing(app, client): + app.testing = False + + @app.errorhandler(500) + def internal_server_error(e): + return "internal server error", 500 + + @app.route("/") + def broken_func(): + raise ZeroDivisionError + + @app.after_request + def after_request(resp): + resp.mimetype = "text/x-special" + return resp + + resp = client.get("/") + assert resp.mimetype == "text/x-special" + assert resp.data == b"internal server error" + + +def test_baseexception_error_handling(app, client): + app.testing = False + + @app.route("/") + def broken_func(): + raise KeyboardInterrupt() + + with pytest.raises(KeyboardInterrupt): + client.get("/") + + +def test_before_request_and_routing_errors(app, client): + @app.before_request + def attach_something(): + flask.g.something = "value" + + @app.errorhandler(404) + def return_something(error): + return flask.g.something, 404 + + rv = client.get("/") + assert rv.status_code == 404 + assert rv.data == b"value" + + +def test_user_error_handling(app, client): + class MyException(Exception): + pass + + @app.errorhandler(MyException) + def handle_my_exception(e): + assert isinstance(e, MyException) + return "42" + + @app.route("/") + def index(): + raise MyException() + + assert client.get("/").data == b"42" + + +def test_http_error_subclass_handling(app, client): + class ForbiddenSubclass(Forbidden): + pass + + @app.errorhandler(ForbiddenSubclass) + def handle_forbidden_subclass(e): + assert isinstance(e, ForbiddenSubclass) + return "banana" + + @app.errorhandler(403) + def handle_403(e): + assert not isinstance(e, ForbiddenSubclass) + assert isinstance(e, Forbidden) + return "apple" + + @app.route("/1") + def index1(): + raise ForbiddenSubclass() + + @app.route("/2") + def index2(): + flask.abort(403) + + @app.route("/3") + def index3(): + raise Forbidden() + + assert client.get("/1").data == b"banana" + assert client.get("/2").data == b"apple" + assert client.get("/3").data == b"apple" + + +def test_errorhandler_precedence(app, client): + class E1(Exception): + pass + + class E2(Exception): + pass + + class E3(E1, E2): + pass + + @app.errorhandler(E2) + def handle_e2(e): + return "E2" + + @app.errorhandler(Exception) + def handle_exception(e): + return "Exception" + + @app.route("/E1") + def raise_e1(): + raise E1 + + @app.route("/E3") + def raise_e3(): + raise E3 + + rv = client.get("/E1") + assert rv.data == b"Exception" + + rv = client.get("/E3") + assert rv.data == b"E2" + + +@pytest.mark.parametrize( + ("debug", "trap", "expect_key", "expect_abort"), + [(False, None, True, True), (True, None, False, True), (False, True, False, False)], +) +def test_trap_bad_request_key_error(app, client, debug, trap, expect_key, expect_abort): + app.config["DEBUG"] = debug + app.config["TRAP_BAD_REQUEST_ERRORS"] = trap + + @app.route("/key") + def fail(): + flask.request.form["missing_key"] + + @app.route("/abort") + def allow_abort(): + flask.abort(400) + + if expect_key: + rv = client.get("/key") + assert rv.status_code == 400 + assert b"missing_key" not in rv.data + else: + with pytest.raises(KeyError) as exc_info: + client.get("/key") + + assert exc_info.errisinstance(BadRequest) + assert "missing_key" in exc_info.value.get_description() + + if expect_abort: + rv = client.get("/abort") + assert rv.status_code == 400 + else: + with pytest.raises(BadRequest): + client.get("/abort") + + +def test_trapping_of_all_http_exceptions(app, client): + app.config["TRAP_HTTP_EXCEPTIONS"] = True + + @app.route("/fail") + def fail(): + flask.abort(404) + + with pytest.raises(NotFound): + client.get("/fail") + + +def test_error_handler_after_processor_error(app, client): + app.testing = False + + @app.before_request + def before_request(): + if _trigger == "before": + raise ZeroDivisionError + + @app.after_request + def after_request(response): + if _trigger == "after": + raise ZeroDivisionError + + return response + + @app.route("/") + def index(): + return "Foo" + + @app.errorhandler(500) + def internal_server_error(e): + return "Hello Server Error", 500 + + for _trigger in "before", "after": + rv = client.get("/") + assert rv.status_code == 500 + assert rv.data == b"Hello Server Error" + + +def test_enctype_debug_helper(app, client): + from flask.debughelpers import DebugFilesKeyError + + app.debug = True + + @app.route("/fail", methods=["POST"]) + def index(): + return flask.request.files["foo"].filename + + with pytest.raises(DebugFilesKeyError) as e: + client.post("/fail", data={"foo": "index.txt"}) + assert "no file contents were transmitted" in str(e.value) + assert "This was submitted: 'index.txt'" in str(e.value) + + +def test_response_types(app, client): + @app.route("/text") + def from_text(): + return "Hällo Wörld" + + @app.route("/bytes") + def from_bytes(): + return "Hällo Wörld".encode() + + @app.route("/full_tuple") + def from_full_tuple(): + return ( + "Meh", + 400, + {"X-Foo": "Testing", "Content-Type": "text/plain; charset=utf-8"}, + ) + + @app.route("/text_headers") + def from_text_headers(): + return "Hello", {"X-Foo": "Test", "Content-Type": "text/plain; charset=utf-8"} + + @app.route("/text_status") + def from_text_status(): + return "Hi, status!", 400 + + @app.route("/response_headers") + def from_response_headers(): + return ( + flask.Response( + "Hello world", 404, {"Content-Type": "text/html", "X-Foo": "Baz"} + ), + {"Content-Type": "text/plain", "X-Foo": "Bar", "X-Bar": "Foo"}, + ) + + @app.route("/response_status") + def from_response_status(): + return app.response_class("Hello world", 400), 500 + + @app.route("/wsgi") + def from_wsgi(): + return NotFound() + + @app.route("/dict") + def from_dict(): + return {"foo": "bar"}, 201 + + @app.route("/list") + def from_list(): + return ["foo", "bar"], 201 + + assert client.get("/text").data == "Hällo Wörld".encode() + assert client.get("/bytes").data == "Hällo Wörld".encode() + + rv = client.get("/full_tuple") + assert rv.data == b"Meh" + assert rv.headers["X-Foo"] == "Testing" + assert rv.status_code == 400 + assert rv.mimetype == "text/plain" + + rv = client.get("/text_headers") + assert rv.data == b"Hello" + assert rv.headers["X-Foo"] == "Test" + assert rv.status_code == 200 + assert rv.mimetype == "text/plain" + + rv = client.get("/text_status") + assert rv.data == b"Hi, status!" + assert rv.status_code == 400 + assert rv.mimetype == "text/html" + + rv = client.get("/response_headers") + assert rv.data == b"Hello world" + assert rv.content_type == "text/plain" + assert rv.headers.getlist("X-Foo") == ["Bar"] + assert rv.headers["X-Bar"] == "Foo" + assert rv.status_code == 404 + + rv = client.get("/response_status") + assert rv.data == b"Hello world" + assert rv.status_code == 500 + + rv = client.get("/wsgi") + assert b"Not Found" in rv.data + assert rv.status_code == 404 + + rv = client.get("/dict") + assert rv.json == {"foo": "bar"} + assert rv.status_code == 201 + + rv = client.get("/list") + assert rv.json == ["foo", "bar"] + assert rv.status_code == 201 + + +def test_response_type_errors(): + app = flask.Flask(__name__) + app.testing = True + + @app.route("/none") + def from_none(): + pass + + @app.route("/small_tuple") + def from_small_tuple(): + return ("Hello",) + + @app.route("/large_tuple") + def from_large_tuple(): + return "Hello", 234, {"X-Foo": "Bar"}, "???" + + @app.route("/bad_type") + def from_bad_type(): + return True + + @app.route("/bad_wsgi") + def from_bad_wsgi(): + return lambda: None + + c = app.test_client() + + with pytest.raises(TypeError) as e: + c.get("/none") + + assert "returned None" in str(e.value) + assert "from_none" in str(e.value) + + with pytest.raises(TypeError) as e: + c.get("/small_tuple") + + assert "tuple must have the form" in str(e.value) + + with pytest.raises(TypeError): + c.get("/large_tuple") + + with pytest.raises(TypeError) as e: + c.get("/bad_type") + + assert "it was a bool" in str(e.value) + + with pytest.raises(TypeError): + c.get("/bad_wsgi") + + +def test_make_response(app, req_ctx): + rv = flask.make_response() + assert rv.status_code == 200 + assert rv.data == b"" + assert rv.mimetype == "text/html" + + rv = flask.make_response("Awesome") + assert rv.status_code == 200 + assert rv.data == b"Awesome" + assert rv.mimetype == "text/html" + + rv = flask.make_response("W00t", 404) + assert rv.status_code == 404 + assert rv.data == b"W00t" + assert rv.mimetype == "text/html" + + rv = flask.make_response(c for c in "Hello") + assert rv.status_code == 200 + assert rv.data == b"Hello" + assert rv.mimetype == "text/html" + + +def test_make_response_with_response_instance(app, req_ctx): + rv = flask.make_response(flask.jsonify({"msg": "W00t"}), 400) + assert rv.status_code == 400 + assert rv.data == b'{"msg":"W00t"}\n' + assert rv.mimetype == "application/json" + + rv = flask.make_response(flask.Response(""), 400) + assert rv.status_code == 400 + assert rv.data == b"" + assert rv.mimetype == "text/html" + + rv = flask.make_response( + flask.Response("", headers={"Content-Type": "text/html"}), + 400, + [("X-Foo", "bar")], + ) + assert rv.status_code == 400 + assert rv.headers["Content-Type"] == "text/html" + assert rv.headers["X-Foo"] == "bar" + + +@pytest.mark.parametrize("compact", [True, False]) +def test_jsonify_no_prettyprint(app, compact): + app.json.compact = compact + rv = app.json.response({"msg": {"submsg": "W00t"}, "msg2": "foobar"}) + data = rv.data.strip() + assert (b" " not in data) is compact + assert (b"\n" not in data) is compact + + +def test_jsonify_mimetype(app, req_ctx): + app.json.mimetype = "application/vnd.api+json" + msg = {"msg": {"submsg": "W00t"}} + rv = flask.make_response(flask.jsonify(msg), 200) + assert rv.mimetype == "application/vnd.api+json" + + +def test_json_dump_dataclass(app, req_ctx): + from dataclasses import make_dataclass + + Data = make_dataclass("Data", [("name", str)]) + value = app.json.dumps(Data("Flask")) + value = app.json.loads(value) + assert value == {"name": "Flask"} + + +def test_jsonify_args_and_kwargs_check(app, req_ctx): + with pytest.raises(TypeError) as e: + flask.jsonify("fake args", kwargs="fake") + assert "args or kwargs" in str(e.value) + + +def test_url_generation(app, req_ctx): + @app.route("/hello/", methods=["POST"]) + def hello(): + pass + + assert flask.url_for("hello", name="test x") == "/hello/test%20x" + assert ( + flask.url_for("hello", name="test x", _external=True) + == "http://localhost/hello/test%20x" + ) + + +def test_build_error_handler(app): + # Test base case, a URL which results in a BuildError. + with app.test_request_context(): + pytest.raises(BuildError, flask.url_for, "spam") + + # Verify the error is re-raised if not the current exception. + try: + with app.test_request_context(): + flask.url_for("spam") + except BuildError as err: + error = err + try: + raise RuntimeError("Test case where BuildError is not current.") + except RuntimeError: + pytest.raises(BuildError, app.handle_url_build_error, error, "spam", {}) + + # Test a custom handler. + def handler(error, endpoint, values): + # Just a test. + return "/test_handler/" + + app.url_build_error_handlers.append(handler) + with app.test_request_context(): + assert flask.url_for("spam") == "/test_handler/" + + +def test_build_error_handler_reraise(app): + # Test a custom handler which reraises the BuildError + def handler_raises_build_error(error, endpoint, values): + raise error + + app.url_build_error_handlers.append(handler_raises_build_error) + + with app.test_request_context(): + pytest.raises(BuildError, flask.url_for, "not.existing") + + +def test_url_for_passes_special_values_to_build_error_handler(app): + @app.url_build_error_handlers.append + def handler(error, endpoint, values): + assert values == { + "_external": False, + "_anchor": None, + "_method": None, + "_scheme": None, + } + return "handled" + + with app.test_request_context(): + flask.url_for("/") + + +def test_static_files(app, client): + rv = client.get("/static/index.html") + assert rv.status_code == 200 + assert rv.data.strip() == b"

Hello World!

" + with app.test_request_context(): + assert flask.url_for("static", filename="index.html") == "/static/index.html" + rv.close() + + +def test_static_url_path(): + app = flask.Flask(__name__, static_url_path="/foo") + app.testing = True + rv = app.test_client().get("/foo/index.html") + assert rv.status_code == 200 + rv.close() + + with app.test_request_context(): + assert flask.url_for("static", filename="index.html") == "/foo/index.html" + + +def test_static_url_path_with_ending_slash(): + app = flask.Flask(__name__, static_url_path="/foo/") + app.testing = True + rv = app.test_client().get("/foo/index.html") + assert rv.status_code == 200 + rv.close() + + with app.test_request_context(): + assert flask.url_for("static", filename="index.html") == "/foo/index.html" + + +def test_static_url_empty_path(app): + app = flask.Flask(__name__, static_folder="", static_url_path="") + rv = app.test_client().open("/static/index.html", method="GET") + assert rv.status_code == 200 + rv.close() + + +def test_static_url_empty_path_default(app): + app = flask.Flask(__name__, static_folder="") + rv = app.test_client().open("/static/index.html", method="GET") + assert rv.status_code == 200 + rv.close() + + +def test_static_folder_with_pathlib_path(app): + from pathlib import Path + + app = flask.Flask(__name__, static_folder=Path("static")) + rv = app.test_client().open("/static/index.html", method="GET") + assert rv.status_code == 200 + rv.close() + + +def test_static_folder_with_ending_slash(): + app = flask.Flask(__name__, static_folder="static/") + + @app.route("/") + def catch_all(path): + return path + + rv = app.test_client().get("/catch/all") + assert rv.data == b"catch/all" + + +def test_static_route_with_host_matching(): + app = flask.Flask(__name__, host_matching=True, static_host="example.com") + c = app.test_client() + rv = c.get("http://example.com/static/index.html") + assert rv.status_code == 200 + rv.close() + with app.test_request_context(): + rv = flask.url_for("static", filename="index.html", _external=True) + assert rv == "http://example.com/static/index.html" + # Providing static_host without host_matching=True should error. + with pytest.raises(AssertionError): + flask.Flask(__name__, static_host="example.com") + # Providing host_matching=True with static_folder + # but without static_host should error. + with pytest.raises(AssertionError): + flask.Flask(__name__, host_matching=True) + # Providing host_matching=True without static_host + # but with static_folder=None should not error. + flask.Flask(__name__, host_matching=True, static_folder=None) + + +def test_request_locals(): + assert repr(flask.g) == "" + assert not flask.g + + +def test_server_name_subdomain(): + app = flask.Flask(__name__, subdomain_matching=True) + client = app.test_client() + + @app.route("/") + def index(): + return "default" + + @app.route("/", subdomain="foo") + def subdomain(): + return "subdomain" + + app.config["SERVER_NAME"] = "dev.local:5000" + rv = client.get("/") + assert rv.data == b"default" + + rv = client.get("/", "http://dev.local:5000") + assert rv.data == b"default" + + rv = client.get("/", "https://dev.local:5000") + assert rv.data == b"default" + + app.config["SERVER_NAME"] = "dev.local:443" + rv = client.get("/", "https://dev.local") + + # Werkzeug 1.0 fixes matching https scheme with 443 port + if rv.status_code != 404: + assert rv.data == b"default" + + app.config["SERVER_NAME"] = "dev.local" + rv = client.get("/", "https://dev.local") + assert rv.data == b"default" + + # suppress Werkzeug 0.15 warning about name mismatch + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Current server name", UserWarning, "flask.app" + ) + rv = client.get("/", "http://foo.localhost") + assert rv.status_code == 404 + + rv = client.get("/", "http://foo.dev.local") + assert rv.data == b"subdomain" + + +@pytest.mark.parametrize("key", ["TESTING", "PROPAGATE_EXCEPTIONS", "DEBUG", None]) +def test_exception_propagation(app, client, key): + app.testing = False + + @app.route("/") + def index(): + raise ZeroDivisionError + + if key is not None: + app.config[key] = True + + with pytest.raises(ZeroDivisionError): + client.get("/") + else: + assert client.get("/").status_code == 500 + + +@pytest.mark.parametrize("debug", [True, False]) +@pytest.mark.parametrize("use_debugger", [True, False]) +@pytest.mark.parametrize("use_reloader", [True, False]) +@pytest.mark.parametrize("propagate_exceptions", [None, True, False]) +def test_werkzeug_passthrough_errors( + monkeypatch, debug, use_debugger, use_reloader, propagate_exceptions, app +): + rv = {} + + # Mocks werkzeug.serving.run_simple method + def run_simple_mock(*args, **kwargs): + rv["passthrough_errors"] = kwargs.get("passthrough_errors") + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + app.config["PROPAGATE_EXCEPTIONS"] = propagate_exceptions + app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) + + +def test_max_content_length(app, client): + app.config["MAX_CONTENT_LENGTH"] = 64 + + @app.before_request + def always_first(): + flask.request.form["myfile"] + AssertionError() + + @app.route("/accept", methods=["POST"]) + def accept_file(): + flask.request.form["myfile"] + AssertionError() + + @app.errorhandler(413) + def catcher(error): + return "42" + + rv = client.post("/accept", data={"myfile": "foo" * 100}) + assert rv.data == b"42" + + +def test_url_processors(app, client): + @app.url_defaults + def add_language_code(endpoint, values): + if flask.g.lang_code is not None and app.url_map.is_endpoint_expecting( + endpoint, "lang_code" + ): + values.setdefault("lang_code", flask.g.lang_code) + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop("lang_code", None) + + @app.route("//") + def index(): + return flask.url_for("about") + + @app.route("//about") + def about(): + return flask.url_for("something_else") + + @app.route("/foo") + def something_else(): + return flask.url_for("about", lang_code="en") + + assert client.get("/de/").data == b"/de/about" + assert client.get("/de/about").data == b"/foo" + assert client.get("/foo").data == b"/en/about" + + +def test_inject_blueprint_url_defaults(app): + bp = flask.Blueprint("foo", __name__, template_folder="template") + + @bp.url_defaults + def bp_defaults(endpoint, values): + values["page"] = "login" + + @bp.route("/") + def view(page): + pass + + app.register_blueprint(bp) + + values = dict() + app.inject_url_defaults("foo.view", values) + expected = dict(page="login") + assert values == expected + + with app.test_request_context("/somepage"): + url = flask.url_for("foo.view") + expected = "/login" + assert url == expected + + +def test_nonascii_pathinfo(app, client): + @app.route("/киртест") + def index(): + return "Hello World!" + + rv = client.get("/киртест") + assert rv.data == b"Hello World!" + + +def test_no_setup_after_first_request(app, client): + app.debug = True + + @app.route("/") + def index(): + return "Awesome" + + assert client.get("/").data == b"Awesome" + + with pytest.raises(AssertionError) as exc_info: + app.add_url_rule("/foo", endpoint="late") + + assert "setup method 'add_url_rule'" in str(exc_info.value) + + +def test_routing_redirect_debugging(monkeypatch, app, client): + app.config["DEBUG"] = True + + @app.route("/user/", methods=["GET", "POST"]) + def user(): + return flask.request.form["status"] + + # default redirect code preserves form data + rv = client.post("/user", data={"status": "success"}, follow_redirects=True) + assert rv.data == b"success" + + # 301 and 302 raise error + monkeypatch.setattr(RequestRedirect, "code", 301) + + with client, pytest.raises(AssertionError) as exc_info: + client.post("/user", data={"status": "error"}, follow_redirects=True) + + assert "canonical URL 'http://localhost/user/'" in str(exc_info.value) + + +def test_route_decorator_custom_endpoint(app, client): + app.debug = True + + @app.route("/foo/") + def foo(): + return flask.request.endpoint + + @app.route("/bar/", endpoint="bar") + def for_bar(): + return flask.request.endpoint + + @app.route("/bar/123", endpoint="123") + def for_bar_foo(): + return flask.request.endpoint + + with app.test_request_context(): + assert flask.url_for("foo") == "/foo/" + assert flask.url_for("bar") == "/bar/" + assert flask.url_for("123") == "/bar/123" + + assert client.get("/foo/").data == b"foo" + assert client.get("/bar/").data == b"bar" + assert client.get("/bar/123").data == b"123" + + +def test_get_method_on_g(app_ctx): + assert flask.g.get("x") is None + assert flask.g.get("x", 11) == 11 + flask.g.x = 42 + assert flask.g.get("x") == 42 + assert flask.g.x == 42 + + +def test_g_iteration_protocol(app_ctx): + flask.g.foo = 23 + flask.g.bar = 42 + assert "foo" in flask.g + assert "foos" not in flask.g + assert sorted(flask.g) == ["bar", "foo"] + + +def test_subdomain_basic_support(): + app = flask.Flask(__name__, subdomain_matching=True) + app.config["SERVER_NAME"] = "localhost.localdomain" + client = app.test_client() + + @app.route("/") + def normal_index(): + return "normal index" + + @app.route("/", subdomain="test") + def test_index(): + return "test index" + + rv = client.get("/", "http://localhost.localdomain/") + assert rv.data == b"normal index" + + rv = client.get("/", "http://test.localhost.localdomain/") + assert rv.data == b"test index" + + +def test_subdomain_matching(): + app = flask.Flask(__name__, subdomain_matching=True) + client = app.test_client() + app.config["SERVER_NAME"] = "localhost.localdomain" + + @app.route("/", subdomain="") + def index(user): + return f"index for {user}" + + rv = client.get("/", "http://mitsuhiko.localhost.localdomain/") + assert rv.data == b"index for mitsuhiko" + + +def test_subdomain_matching_with_ports(): + app = flask.Flask(__name__, subdomain_matching=True) + app.config["SERVER_NAME"] = "localhost.localdomain:3000" + client = app.test_client() + + @app.route("/", subdomain="") + def index(user): + return f"index for {user}" + + rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/") + assert rv.data == b"index for mitsuhiko" + + +@pytest.mark.parametrize("matching", (False, True)) +def test_subdomain_matching_other_name(matching): + app = flask.Flask(__name__, subdomain_matching=matching) + app.config["SERVER_NAME"] = "localhost.localdomain:3000" + client = app.test_client() + + @app.route("/") + def index(): + return "", 204 + + # suppress Werkzeug 0.15 warning about name mismatch + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Current server name", UserWarning, "flask.app" + ) + # ip address can't match name + rv = client.get("/", "http://127.0.0.1:3000/") + assert rv.status_code == 404 if matching else 204 + + # allow all subdomains if matching is disabled + rv = client.get("/", "http://www.localhost.localdomain:3000/") + assert rv.status_code == 404 if matching else 204 + + +def test_multi_route_rules(app, client): + @app.route("/") + @app.route("//") + def index(test="a"): + return test + + rv = client.open("/") + assert rv.data == b"a" + rv = client.open("/b/") + assert rv.data == b"b" + + +def test_multi_route_class_views(app, client): + class View: + def __init__(self, app): + app.add_url_rule("/", "index", self.index) + app.add_url_rule("//", "index", self.index) + + def index(self, test="a"): + return test + + _ = View(app) + rv = client.open("/") + assert rv.data == b"a" + rv = client.open("/b/") + assert rv.data == b"b" + + +def test_run_defaults(monkeypatch, app): + rv = {} + + # Mocks werkzeug.serving.run_simple method + def run_simple_mock(*args, **kwargs): + rv["result"] = "running..." + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + app.run() + assert rv["result"] == "running..." + + +def test_run_server_port(monkeypatch, app): + rv = {} + + # Mocks werkzeug.serving.run_simple method + def run_simple_mock(hostname, port, application, *args, **kwargs): + rv["result"] = f"running on {hostname}:{port} ..." + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + hostname, port = "localhost", 8000 + app.run(hostname, port, debug=True) + assert rv["result"] == f"running on {hostname}:{port} ..." + + +@pytest.mark.parametrize( + "host,port,server_name,expect_host,expect_port", + ( + (None, None, "pocoo.org:8080", "pocoo.org", 8080), + ("localhost", None, "pocoo.org:8080", "localhost", 8080), + (None, 80, "pocoo.org:8080", "pocoo.org", 80), + ("localhost", 80, "pocoo.org:8080", "localhost", 80), + ("localhost", 0, "localhost:8080", "localhost", 0), + (None, None, "localhost:8080", "localhost", 8080), + (None, None, "localhost:0", "localhost", 0), + ), +) +def test_run_from_config( + monkeypatch, host, port, server_name, expect_host, expect_port, app +): + def run_simple_mock(hostname, port, *args, **kwargs): + assert hostname == expect_host + assert port == expect_port + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + app.config["SERVER_NAME"] = server_name + app.run(host, port) + + +def test_max_cookie_size(app, client, recwarn): + app.config["MAX_COOKIE_SIZE"] = 100 + + # outside app context, default to Werkzeug static value, + # which is also the default config + response = flask.Response() + default = flask.Flask.default_config["MAX_COOKIE_SIZE"] + assert response.max_cookie_size == default + + # inside app context, use app config + with app.app_context(): + assert flask.Response().max_cookie_size == 100 + + @app.route("/") + def index(): + r = flask.Response("", status=204) + r.set_cookie("foo", "bar" * 100) + return r + + client.get("/") + assert len(recwarn) == 1 + w = recwarn.pop() + assert "cookie is too large" in str(w.message) + + app.config["MAX_COOKIE_SIZE"] = 0 + + client.get("/") + assert len(recwarn) == 0 + + +@require_cpython_gc +def test_app_freed_on_zero_refcount(): + # A Flask instance should not create a reference cycle that prevents CPython + # from freeing it when all external references to it are released (see #3761). + gc.disable() + try: + app = flask.Flask(__name__) + assert app.view_functions["static"] + weak = weakref.ref(app) + assert weak() is not None + del app + assert weak() is None + finally: + gc.enable() + + +Filepath: githubCode\tests\test_blueprints.py: + +import pytest +from jinja2 import TemplateNotFound +from werkzeug.http import parse_cache_control_header + +import flask + + +def test_blueprint_specific_error_handling(app, client): + frontend = flask.Blueprint("frontend", __name__) + backend = flask.Blueprint("backend", __name__) + sideend = flask.Blueprint("sideend", __name__) + + @frontend.errorhandler(403) + def frontend_forbidden(e): + return "frontend says no", 403 + + @frontend.route("/frontend-no") + def frontend_no(): + flask.abort(403) + + @backend.errorhandler(403) + def backend_forbidden(e): + return "backend says no", 403 + + @backend.route("/backend-no") + def backend_no(): + flask.abort(403) + + @sideend.route("/what-is-a-sideend") + def sideend_no(): + flask.abort(403) + + app.register_blueprint(frontend) + app.register_blueprint(backend) + app.register_blueprint(sideend) + + @app.errorhandler(403) + def app_forbidden(e): + return "application itself says no", 403 + + assert client.get("/frontend-no").data == b"frontend says no" + assert client.get("/backend-no").data == b"backend says no" + assert client.get("/what-is-a-sideend").data == b"application itself says no" + + +def test_blueprint_specific_user_error_handling(app, client): + class MyDecoratorException(Exception): + pass + + class MyFunctionException(Exception): + pass + + blue = flask.Blueprint("blue", __name__) + + @blue.errorhandler(MyDecoratorException) + def my_decorator_exception_handler(e): + assert isinstance(e, MyDecoratorException) + return "boom" + + def my_function_exception_handler(e): + assert isinstance(e, MyFunctionException) + return "bam" + + blue.register_error_handler(MyFunctionException, my_function_exception_handler) + + @blue.route("/decorator") + def blue_deco_test(): + raise MyDecoratorException() + + @blue.route("/function") + def blue_func_test(): + raise MyFunctionException() + + app.register_blueprint(blue) + + assert client.get("/decorator").data == b"boom" + assert client.get("/function").data == b"bam" + + +def test_blueprint_app_error_handling(app, client): + errors = flask.Blueprint("errors", __name__) + + @errors.app_errorhandler(403) + def forbidden_handler(e): + return "you shall not pass", 403 + + @app.route("/forbidden") + def app_forbidden(): + flask.abort(403) + + forbidden_bp = flask.Blueprint("forbidden_bp", __name__) + + @forbidden_bp.route("/nope") + def bp_forbidden(): + flask.abort(403) + + app.register_blueprint(errors) + app.register_blueprint(forbidden_bp) + + assert client.get("/forbidden").data == b"you shall not pass" + assert client.get("/nope").data == b"you shall not pass" + + +@pytest.mark.parametrize( + ("prefix", "rule", "url"), + ( + ("", "/", "/"), + ("/", "", "/"), + ("/", "/", "/"), + ("/foo", "", "/foo"), + ("/foo/", "", "/foo/"), + ("", "/bar", "/bar"), + ("/foo/", "/bar", "/foo/bar"), + ("/foo/", "bar", "/foo/bar"), + ("/foo", "/bar", "/foo/bar"), + ("/foo/", "//bar", "/foo/bar"), + ("/foo//", "/bar", "/foo/bar"), + ), +) +def test_blueprint_prefix_slash(app, client, prefix, rule, url): + bp = flask.Blueprint("test", __name__, url_prefix=prefix) + + @bp.route(rule) + def index(): + return "", 204 + + app.register_blueprint(bp) + assert client.get(url).status_code == 204 + + +def test_blueprint_url_defaults(app, client): + bp = flask.Blueprint("test", __name__) + + @bp.route("/foo", defaults={"baz": 42}) + def foo(bar, baz): + return f"{bar}/{baz:d}" + + @bp.route("/bar") + def bar(bar): + return str(bar) + + app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23}) + app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19}) + + assert client.get("/1/foo").data == b"23/42" + assert client.get("/2/foo").data == b"19/42" + assert client.get("/1/bar").data == b"23" + assert client.get("/2/bar").data == b"19" + + +def test_blueprint_url_processors(app, client): + bp = flask.Blueprint("frontend", __name__, url_prefix="/") + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault("lang_code", flask.g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop("lang_code") + + @bp.route("/") + def index(): + return flask.url_for(".about") + + @bp.route("/about") + def about(): + return flask.url_for(".index") + + app.register_blueprint(bp) + + assert client.get("/de/").data == b"/de/about" + assert client.get("/de/about").data == b"/de/" + + +def test_templates_and_static(test_apps): + from blueprintapp import app + + client = app.test_client() + + rv = client.get("/") + assert rv.data == b"Hello from the Frontend" + rv = client.get("/admin/") + assert rv.data == b"Hello from the Admin" + rv = client.get("/admin/index2") + assert rv.data == b"Hello from the Admin" + rv = client.get("/admin/static/test.txt") + assert rv.data.strip() == b"Admin File" + rv.close() + rv = client.get("/admin/static/css/test.css") + assert rv.data.strip() == b"/* nested file */" + rv.close() + + # try/finally, in case other tests use this app for Blueprint tests. + max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"] + try: + expected_max_age = 3600 + if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == expected_max_age: + expected_max_age = 7200 + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = expected_max_age + rv = client.get("/admin/static/css/test.css") + cc = parse_cache_control_header(rv.headers["Cache-Control"]) + assert cc.max_age == expected_max_age + rv.close() + finally: + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default + + with app.test_request_context(): + assert ( + flask.url_for("admin.static", filename="test.txt") + == "/admin/static/test.txt" + ) + + with app.test_request_context(): + with pytest.raises(TemplateNotFound) as e: + flask.render_template("missing.html") + assert e.value.name == "missing.html" + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template("nested/nested.txt") == "I'm nested" + + +def test_default_static_max_age(app): + class MyBlueprint(flask.Blueprint): + def get_send_file_max_age(self, filename): + return 100 + + blueprint = MyBlueprint("blueprint", __name__, static_folder="static") + app.register_blueprint(blueprint) + + # try/finally, in case other tests use this app for Blueprint tests. + max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"] + try: + with app.test_request_context(): + unexpected_max_age = 3600 + if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == unexpected_max_age: + unexpected_max_age = 7200 + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = unexpected_max_age + rv = blueprint.send_static_file("index.html") + cc = parse_cache_control_header(rv.headers["Cache-Control"]) + assert cc.max_age == 100 + rv.close() + finally: + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default + + +def test_templates_list(test_apps): + from blueprintapp import app + + templates = sorted(app.jinja_env.list_templates()) + assert templates == ["admin/index.html", "frontend/index.html"] + + +def test_dotted_name_not_allowed(app, client): + with pytest.raises(ValueError): + flask.Blueprint("app.ui", __name__) + + +def test_empty_name_not_allowed(app, client): + with pytest.raises(ValueError): + flask.Blueprint("", __name__) + + +def test_dotted_names_from_app(app, client): + test = flask.Blueprint("test", __name__) + + @app.route("/") + def app_index(): + return flask.url_for("test.index") + + @test.route("/test/") + def index(): + return flask.url_for("app_index") + + app.register_blueprint(test) + + rv = client.get("/") + assert rv.data == b"/test/" + + +def test_empty_url_defaults(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.route("/", defaults={"page": 1}) + @bp.route("/page/") + def something(page): + return str(page) + + app.register_blueprint(bp) + + assert client.get("/").data == b"1" + assert client.get("/page/2").data == b"2" + + +def test_route_decorator_custom_endpoint(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.route("/foo") + def foo(): + return flask.request.endpoint + + @bp.route("/bar", endpoint="bar") + def foo_bar(): + return flask.request.endpoint + + @bp.route("/bar/123", endpoint="123") + def foo_bar_foo(): + return flask.request.endpoint + + @bp.route("/bar/foo") + def bar_foo(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.request.endpoint + + assert client.get("/").data == b"index" + assert client.get("/py/foo").data == b"bp.foo" + assert client.get("/py/bar").data == b"bp.bar" + assert client.get("/py/bar/123").data == b"bp.123" + assert client.get("/py/bar/foo").data == b"bp.bar_foo" + + +def test_route_decorator_custom_endpoint_with_dots(app, client): + bp = flask.Blueprint("bp", __name__) + + with pytest.raises(ValueError): + bp.route("/", endpoint="a.b")(lambda: "") + + with pytest.raises(ValueError): + bp.add_url_rule("/", endpoint="a.b") + + def view(): + return "" + + view.__name__ = "a.b" + + with pytest.raises(ValueError): + bp.add_url_rule("/", view_func=view) + + +def test_endpoint_decorator(app, client): + from werkzeug.routing import Rule + + app.url_map.add(Rule("/foo", endpoint="bar")) + + bp = flask.Blueprint("bp", __name__) + + @bp.endpoint("bar") + def foobar(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix="/bp_prefix") + + assert client.get("/foo").data == b"bar" + assert client.get("/bp_prefix/bar").status_code == 404 + + +def test_template_filter(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter() + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + 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): + bp = flask.Blueprint("bp", __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse) + app.register_blueprint(bp, url_prefix="/py") + 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): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter("strrev") + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + 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): + bp = flask.Blueprint("bp", __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse, "strrev") + app.register_blueprint(bp, url_prefix="/py") + 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): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + + @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_after_route_with_template(app, client): + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_add_template_filter_with_template(app, client): + bp = flask.Blueprint("bp", __name__) + + def super_reverse(s): + return s[::-1] + + bp.add_app_template_filter(super_reverse) + app.register_blueprint(bp, url_prefix="/py") + + @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): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter("super_reverse") + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + + @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): + bp = flask.Blueprint("bp", __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse, "super_reverse") + app.register_blueprint(bp, url_prefix="/py") + + @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): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test() + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + assert "is_boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["is_boolean"] == is_boolean + assert app.jinja_env.tests["is_boolean"](False) + + +def test_add_template_test(app): + bp = flask.Blueprint("bp", __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean) + app.register_blueprint(bp, url_prefix="/py") + assert "is_boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["is_boolean"] == is_boolean + assert app.jinja_env.tests["is_boolean"](False) + + +def test_template_test_with_name(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test("boolean") + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + 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): + bp = flask.Blueprint("bp", __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean, "boolean") + app.register_blueprint(bp, url_prefix="/py") + 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): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + + @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_after_route_with_template(app, client): + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_add_template_test_with_template(app, client): + bp = flask.Blueprint("bp", __name__) + + def boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(boolean) + app.register_blueprint(bp, url_prefix="/py") + + @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): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test("boolean") + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + + @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): + bp = flask.Blueprint("bp", __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean, "boolean") + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_context_processing(app, client): + answer_bp = flask.Blueprint("answer_bp", __name__) + + def template_string(): + return flask.render_template_string( + "{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}" + "{% if answer %}{{ answer }} is the answer.{% endif %}" + ) + + # App global context processor + @answer_bp.app_context_processor + def not_answer_context_processor(): + return {"notanswer": 43} + + # Blueprint local context processor + @answer_bp.context_processor + def answer_context_processor(): + return {"answer": 42} + + # Setup endpoints for testing + @answer_bp.route("/bp") + def bp_page(): + return template_string() + + @app.route("/") + def app_page(): + return template_string() + + # Register the blueprint + app.register_blueprint(answer_bp) + + app_page_bytes = client.get("/").data + answer_page_bytes = client.get("/bp").data + + assert b"43" in app_page_bytes + assert b"42" not in app_page_bytes + + assert b"42" in answer_page_bytes + assert b"43" in answer_page_bytes + + +def test_template_global(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_global() + def get_answer(): + return 42 + + # Make sure the function is not in the jinja_env already + assert "get_answer" not in app.jinja_env.globals.keys() + app.register_blueprint(bp) + + # Tests + assert "get_answer" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["get_answer"] is get_answer + assert app.jinja_env.globals["get_answer"]() == 42 + + with app.app_context(): + rv = flask.render_template_string("{{ get_answer() }}") + assert rv == "42" + + +def test_request_processing(app, client): + bp = flask.Blueprint("bp", __name__) + evts = [] + + @bp.before_request + def before_bp(): + evts.append("before") + + @bp.after_request + def after_bp(response): + response.data += b"|after" + evts.append("after") + return response + + @bp.teardown_request + def teardown_bp(exc): + evts.append("teardown") + + # Setup routes for testing + @bp.route("/bp") + def bp_endpoint(): + return "request" + + app.register_blueprint(bp) + + assert evts == [] + rv = client.get("/bp") + assert rv.data == b"request|after" + assert evts == ["before", "after", "teardown"] + + +def test_app_request_processing(app, client): + bp = flask.Blueprint("bp", __name__) + evts = [] + + @bp.before_app_request + def before_app(): + evts.append("before") + + @bp.after_app_request + def after_app(response): + response.data += b"|after" + evts.append("after") + return response + + @bp.teardown_app_request + def teardown_app(exc): + evts.append("teardown") + + app.register_blueprint(bp) + + # Setup routes for testing + @app.route("/") + def bp_endpoint(): + return "request" + + # before first request + assert evts == [] + + # first request + resp = client.get("/").data + assert resp == b"request|after" + assert evts == ["before", "after", "teardown"] + + # second request + resp = client.get("/").data + assert resp == b"request|after" + assert evts == ["before", "after", "teardown"] * 2 + + +def test_app_url_processors(app, client): + bp = flask.Blueprint("bp", __name__) + + # Register app-wide url defaults and preprocessor on blueprint + @bp.app_url_defaults + def add_language_code(endpoint, values): + values.setdefault("lang_code", flask.g.lang_code) + + @bp.app_url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop("lang_code") + + # Register route rules at the app level + @app.route("//") + def index(): + return flask.url_for("about") + + @app.route("//about") + def about(): + return flask.url_for("index") + + app.register_blueprint(bp) + + assert client.get("/de/").data == b"/de/about" + assert client.get("/de/about").data == b"/de/" + + +def test_nested_blueprint(app, client): + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + grandchild = flask.Blueprint("grandchild", __name__) + + @parent.errorhandler(403) + def forbidden(e): + return "Parent no", 403 + + @parent.route("/") + def parent_index(): + return "Parent yes" + + @parent.route("/no") + def parent_no(): + flask.abort(403) + + @child.route("/") + def child_index(): + return "Child yes" + + @child.route("/no") + def child_no(): + flask.abort(403) + + @grandchild.errorhandler(403) + def grandchild_forbidden(e): + return "Grandchild no", 403 + + @grandchild.route("/") + def grandchild_index(): + return "Grandchild yes" + + @grandchild.route("/no") + def grandchild_no(): + flask.abort(403) + + child.register_blueprint(grandchild, url_prefix="/grandchild") + parent.register_blueprint(child, url_prefix="/child") + app.register_blueprint(parent, url_prefix="/parent") + + assert client.get("/parent/").data == b"Parent yes" + assert client.get("/parent/child/").data == b"Child yes" + assert client.get("/parent/child/grandchild/").data == b"Grandchild yes" + assert client.get("/parent/no").data == b"Parent no" + assert client.get("/parent/child/no").data == b"Parent no" + assert client.get("/parent/child/grandchild/no").data == b"Grandchild no" + + +def test_nested_callback_order(app, client): + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + + @app.before_request + def app_before1(): + flask.g.setdefault("seen", []).append("app_1") + + @app.teardown_request + def app_teardown1(e=None): + assert flask.g.seen.pop() == "app_1" + + @app.before_request + def app_before2(): + flask.g.setdefault("seen", []).append("app_2") + + @app.teardown_request + def app_teardown2(e=None): + assert flask.g.seen.pop() == "app_2" + + @app.context_processor + def app_ctx(): + return dict(key="app") + + @parent.before_request + def parent_before1(): + flask.g.setdefault("seen", []).append("parent_1") + + @parent.teardown_request + def parent_teardown1(e=None): + assert flask.g.seen.pop() == "parent_1" + + @parent.before_request + def parent_before2(): + flask.g.setdefault("seen", []).append("parent_2") + + @parent.teardown_request + def parent_teardown2(e=None): + assert flask.g.seen.pop() == "parent_2" + + @parent.context_processor + def parent_ctx(): + return dict(key="parent") + + @child.before_request + def child_before1(): + flask.g.setdefault("seen", []).append("child_1") + + @child.teardown_request + def child_teardown1(e=None): + assert flask.g.seen.pop() == "child_1" + + @child.before_request + def child_before2(): + flask.g.setdefault("seen", []).append("child_2") + + @child.teardown_request + def child_teardown2(e=None): + assert flask.g.seen.pop() == "child_2" + + @child.context_processor + def child_ctx(): + return dict(key="child") + + @child.route("/a") + def a(): + return ", ".join(flask.g.seen) + + @child.route("/b") + def b(): + return flask.render_template_string("{{ key }}") + + parent.register_blueprint(child) + app.register_blueprint(parent) + assert ( + client.get("/a").data == b"app_1, app_2, parent_1, parent_2, child_1, child_2" + ) + assert client.get("/b").data == b"child" + + +@pytest.mark.parametrize( + "parent_init, child_init, parent_registration, child_registration", + [ + ("/parent", "/child", None, None), + ("/parent", None, None, "/child"), + (None, None, "/parent", "/child"), + ("/other", "/something", "/parent", "/child"), + ], +) +def test_nesting_url_prefixes( + parent_init, + child_init, + parent_registration, + child_registration, + app, + client, +) -> None: + parent = flask.Blueprint("parent", __name__, url_prefix=parent_init) + child = flask.Blueprint("child", __name__, url_prefix=child_init) + + @child.route("/") + def index(): + return "index" + + parent.register_blueprint(child, url_prefix=child_registration) + app.register_blueprint(parent, url_prefix=parent_registration) + + response = client.get("/parent/child/") + assert response.status_code == 200 + + +def test_nesting_subdomains(app, client) -> None: + subdomain = "api" + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + + @child.route("/child/") + def index(): + return "child" + + parent.register_blueprint(child) + app.register_blueprint(parent, subdomain=subdomain) + + client.allow_subdomain_redirects = True + + domain_name = "domain.tld" + app.config["SERVER_NAME"] = domain_name + response = client.get("/child/", base_url="http://api." + domain_name) + + assert response.status_code == 200 + + +def test_child_and_parent_subdomain(app, client) -> None: + child_subdomain = "api" + parent_subdomain = "parent" + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__, subdomain=child_subdomain) + + @child.route("/") + def index(): + return "child" + + parent.register_blueprint(child) + app.register_blueprint(parent, subdomain=parent_subdomain) + + client.allow_subdomain_redirects = True + + domain_name = "domain.tld" + app.config["SERVER_NAME"] = domain_name + response = client.get( + "/", base_url=f"http://{child_subdomain}.{parent_subdomain}.{domain_name}" + ) + + assert response.status_code == 200 + + response = client.get("/", base_url=f"http://{parent_subdomain}.{domain_name}") + + assert response.status_code == 404 + + +def test_unique_blueprint_names(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + bp2 = flask.Blueprint("bp", __name__) + + app.register_blueprint(bp) + + with pytest.raises(ValueError): + app.register_blueprint(bp) # same bp, same name, error + + app.register_blueprint(bp, name="again") # same bp, different name, ok + + with pytest.raises(ValueError): + app.register_blueprint(bp2) # different bp, same name, error + + app.register_blueprint(bp2, name="alt") # different bp, different name, ok + + +def test_self_registration(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + with pytest.raises(ValueError): + bp.register_blueprint(bp) + + +def test_blueprint_renaming(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + bp2 = flask.Blueprint("bp2", __name__) + + @bp.get("/") + def index(): + return flask.request.endpoint + + @bp.get("/error") + def error(): + flask.abort(403) + + @bp.errorhandler(403) + def forbidden(_: Exception): + return "Error", 403 + + @bp2.get("/") + def index2(): + return flask.request.endpoint + + bp.register_blueprint(bp2, url_prefix="/a", name="sub") + app.register_blueprint(bp, url_prefix="/a") + app.register_blueprint(bp, url_prefix="/b", name="alt") + + assert client.get("/a/").data == b"bp.index" + assert client.get("/b/").data == b"alt.index" + assert client.get("/a/a/").data == b"bp.sub.index2" + assert client.get("/b/a/").data == b"alt.sub.index2" + assert client.get("/a/error").data == b"Error" + assert client.get("/b/error").data == b"Error" + + +Filepath: githubCode\tests\test_cli.py: + +# 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//", + 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__] + + +Filepath: githubCode\tests\test_config.py: + +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öö" + + +Filepath: githubCode\tests\test_converters.py: + +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("/") + 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("/") + def index(name): + return name + + assert client.get("/admin").data == b"admin" + + +Filepath: githubCode\tests\test_helpers.py: + +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/", 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("/") + 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(self, mode): + app = flask.Flask(__name__) + + with app.open_resource("static/index.html", mode) as f: + assert "

Hello World!

" in str(f.read()) + + @pytest.mark.parametrize("mode", ("w", "x", "a", "r+")) + def test_open_resource_exceptions(self, mode): + app = flask.Flask(__name__) + + with pytest.raises(ValueError): + app.open_resource("static/index.html", mode) + + +Filepath: githubCode\tests\test_instance_config.py: + +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" + ) + + +Filepath: githubCode\tests\test_json.py: + +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": "", "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 "

test

" + + result = json.dumps(ObjectWithHTML()) + assert result == '"

test

"' + + +Filepath: githubCode\tests\test_json_tag.py: + +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(""), + 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) + + +Filepath: githubCode\tests\test_logging.py: + +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 + + +Filepath: githubCode\tests\test_regression.py: + +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" + + +Filepath: githubCode\tests\test_reqctx.py: + +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 + + +Filepath: githubCode\tests\test_session_interface.py: + +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 + + +Filepath: githubCode\tests\test_signals.py: + +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"

43

" + 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) + + +Filepath: githubCode\tests\test_subclassing.py: + +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() + + +Filepath: githubCode\tests\test_templating.py: + +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"

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

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"<p>Hello World!", + b"

Hello World!", + b"

Hello World!", + b"

Hello World!", + b"<p>Hello World!", + b"

Hello World!", + ] + + +def test_no_escaping(app, client): + text = "

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"

Hello World!", + b"

Hello World!", + b"

Hello World!", + b"

Hello World!", + b"<p>Hello World!", + b"

Hello World!", + b"

Hello World!", + b"

Hello World!", + ] + + +def test_escaping_without_template_filename(app, client, req_ctx): + assert flask.render_template_string("{{ foo }}", foo="") == "<test>" + assert flask.render_template("mail.txt", foo="") == " 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"

Jameson

" + + +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) + + +Filepath: githubCode\tests\test_testing.py: + +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", "") + + with client: + rv = client.get("/getsession") + assert rv.data == b"" + + 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="") + 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("/") + 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 + + +Filepath: githubCode\tests\test_user_error_handler.py: + +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" + + +Filepath: githubCode\tests\test_views.py: + +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" + + +Filepath: githubCode\examples\celery\make_celery.py: + +from task_app import create_app + +flask_app = create_app() +celery_app = flask_app.extensions["celery"] + + +Filepath: githubCode\examples\celery\src\task_app\tasks.py: + +import time + +from celery import shared_task +from celery import Task + + +@shared_task(ignore_result=False) +def add(a: int, b: int) -> int: + return a + b + + +@shared_task() +def block() -> None: + time.sleep(5) + + +@shared_task(bind=True, ignore_result=False) +def process(self: Task, total: int) -> object: + for i in range(total): + self.update_state(state="PROGRESS", meta={"current": i + 1, "total": total}) + time.sleep(1) + + return {"current": total, "total": total} + + +Filepath: githubCode\examples\celery\src\task_app\views.py: + +from celery.result import AsyncResult +from flask import Blueprint +from flask import request + +from . import tasks + +bp = Blueprint("tasks", __name__, url_prefix="/tasks") + + +@bp.get("/result/") +def result(id: str) -> dict[str, object]: + result = AsyncResult(id) + ready = result.ready() + return { + "ready": ready, + "successful": result.successful() if ready else None, + "value": result.get() if ready else result.result, + } + + +@bp.post("/add") +def add() -> dict[str, object]: + a = request.form.get("a", type=int) + b = request.form.get("b", type=int) + result = tasks.add.delay(a, b) + return {"result_id": result.id} + + +@bp.post("/block") +def block() -> dict[str, object]: + result = tasks.block.delay() + return {"result_id": result.id} + + +@bp.post("/process") +def process() -> dict[str, object]: + result = tasks.process.delay(total=request.form.get("total", type=int)) + return {"result_id": result.id} + + +Filepath: githubCode\examples\celery\src\task_app\__init__.py: + +from celery import Celery +from celery import Task +from flask import Flask +from flask import render_template + + +def create_app() -> Flask: + app = Flask(__name__) + app.config.from_mapping( + CELERY=dict( + broker_url="redis://localhost", + result_backend="redis://localhost", + task_ignore_result=True, + ), + ) + app.config.from_prefixed_env() + celery_init_app(app) + + @app.route("/") + def index() -> str: + return render_template("index.html") + + from . import views + + app.register_blueprint(views.bp) + return app + + +def celery_init_app(app: Flask) -> Celery: + class FlaskTask(Task): + def __call__(self, *args: object, **kwargs: object) -> object: + with app.app_context(): + return self.run(*args, **kwargs) + + celery_app = Celery(app.name, task_cls=FlaskTask) + celery_app.config_from_object(app.config["CELERY"]) + celery_app.set_default() + app.extensions["celery"] = celery_app + return celery_app + + +Filepath: githubCode\examples\javascript\js_example\views.py: + +from flask import jsonify +from flask import render_template +from flask import request + +from . import app + + +@app.route("/", defaults={"js": "fetch"}) +@app.route("/") +def index(js): + return render_template(f"{js}.html", js=js) + + +@app.route("/add", methods=["POST"]) +def add(): + a = request.form.get("a", 0, type=float) + b = request.form.get("b", 0, type=float) + return jsonify(result=a + b) + + +Filepath: githubCode\examples\javascript\js_example\__init__.py: + +from flask import Flask + +app = Flask(__name__) + +from js_example import views # noqa: E402, F401 + + +Filepath: githubCode\examples\javascript\tests\conftest.py: + +import pytest + +from js_example import app + + +@pytest.fixture(name="app") +def fixture_app(): + app.testing = True + yield app + app.testing = False + + +@pytest.fixture +def client(app): + return app.test_client() + + +Filepath: githubCode\examples\javascript\tests\test_js_example.py: + +import pytest +from flask import template_rendered + + +@pytest.mark.parametrize( + ("path", "template_name"), + ( + ("/", "xhr.html"), + ("/plain", "xhr.html"), + ("/fetch", "fetch.html"), + ("/jquery", "jquery.html"), + ), +) +def test_index(app, client, path, template_name): + def check(sender, template, context): + assert template.name == template_name + + with template_rendered.connected_to(check, app): + client.get(path) + + +@pytest.mark.parametrize( + ("a", "b", "result"), ((2, 3, 5), (2.5, 3, 5.5), (2, None, 2), (2, "b", 2)) +) +def test_add(client, a, b, result): + response = client.post("/add", data={"a": a, "b": b}) + assert response.get_json()["result"] == result + + +Filepath: githubCode\examples\tutorial\flaskr\auth.py: + +import functools + +from flask import Blueprint +from flask import flash +from flask import g +from flask import redirect +from flask import render_template +from flask import request +from flask import session +from flask import url_for +from werkzeug.security import check_password_hash +from werkzeug.security import generate_password_hash + +from .db import get_db + +bp = Blueprint("auth", __name__, url_prefix="/auth") + + +def login_required(view): + """View decorator that redirects anonymous users to the login page.""" + + @functools.wraps(view) + def wrapped_view(**kwargs): + if g.user is None: + return redirect(url_for("auth.login")) + + return view(**kwargs) + + return wrapped_view + + +@bp.before_app_request +def load_logged_in_user(): + """If a user id is stored in the session, load the user object from + the database into ``g.user``.""" + user_id = session.get("user_id") + + if user_id is None: + g.user = None + else: + g.user = ( + get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone() + ) + + +@bp.route("/register", methods=("GET", "POST")) +def register(): + """Register a new user. + + Validates that the username is not already taken. Hashes the + password for security. + """ + if request.method == "POST": + username = request.form["username"] + password = request.form["password"] + db = get_db() + error = None + + if not username: + error = "Username is required." + elif not password: + error = "Password is required." + + if error is None: + try: + db.execute( + "INSERT INTO user (username, password) VALUES (?, ?)", + (username, generate_password_hash(password)), + ) + db.commit() + except db.IntegrityError: + # The username was already taken, which caused the + # commit to fail. Show a validation error. + error = f"User {username} is already registered." + else: + # Success, go to the login page. + return redirect(url_for("auth.login")) + + flash(error) + + return render_template("auth/register.html") + + +@bp.route("/login", methods=("GET", "POST")) +def login(): + """Log in a registered user by adding the user id to the session.""" + if request.method == "POST": + username = request.form["username"] + password = request.form["password"] + db = get_db() + error = None + user = db.execute( + "SELECT * FROM user WHERE username = ?", (username,) + ).fetchone() + + if user is None: + error = "Incorrect username." + elif not check_password_hash(user["password"], password): + error = "Incorrect password." + + if error is None: + # store the user id in a new session and return to the index + session.clear() + session["user_id"] = user["id"] + return redirect(url_for("index")) + + flash(error) + + return render_template("auth/login.html") + + +@bp.route("/logout") +def logout(): + """Clear the current session, including the stored user id.""" + session.clear() + return redirect(url_for("index")) + + +Filepath: githubCode\examples\tutorial\flaskr\blog.py: + +from flask import Blueprint +from flask import flash +from flask import g +from flask import redirect +from flask import render_template +from flask import request +from flask import url_for +from werkzeug.exceptions import abort + +from .auth import login_required +from .db import get_db + +bp = Blueprint("blog", __name__) + + +@bp.route("/") +def index(): + """Show all the posts, most recent first.""" + db = get_db() + posts = db.execute( + "SELECT p.id, title, body, created, author_id, username" + " FROM post p JOIN user u ON p.author_id = u.id" + " ORDER BY created DESC" + ).fetchall() + return render_template("blog/index.html", posts=posts) + + +def get_post(id, check_author=True): + """Get a post and its author by id. + + Checks that the id exists and optionally that the current user is + the author. + + :param id: id of post to get + :param check_author: require the current user to be the author + :return: the post with author information + :raise 404: if a post with the given id doesn't exist + :raise 403: if the current user isn't the author + """ + post = ( + get_db() + .execute( + "SELECT p.id, title, body, created, author_id, username" + " FROM post p JOIN user u ON p.author_id = u.id" + " WHERE p.id = ?", + (id,), + ) + .fetchone() + ) + + if post is None: + abort(404, f"Post id {id} doesn't exist.") + + if check_author and post["author_id"] != g.user["id"]: + abort(403) + + return post + + +@bp.route("/create", methods=("GET", "POST")) +@login_required +def create(): + """Create a new post for the current user.""" + if request.method == "POST": + title = request.form["title"] + body = request.form["body"] + error = None + + if not title: + error = "Title is required." + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + "INSERT INTO post (title, body, author_id) VALUES (?, ?, ?)", + (title, body, g.user["id"]), + ) + db.commit() + return redirect(url_for("blog.index")) + + return render_template("blog/create.html") + + +@bp.route("//update", methods=("GET", "POST")) +@login_required +def update(id): + """Update a post if the current user is the author.""" + post = get_post(id) + + if request.method == "POST": + title = request.form["title"] + body = request.form["body"] + error = None + + if not title: + error = "Title is required." + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + "UPDATE post SET title = ?, body = ? WHERE id = ?", (title, body, id) + ) + db.commit() + return redirect(url_for("blog.index")) + + return render_template("blog/update.html", post=post) + + +@bp.route("//delete", methods=("POST",)) +@login_required +def delete(id): + """Delete a post. + + Ensures that the post exists and that the logged in user is the + author of the post. + """ + get_post(id) + db = get_db() + db.execute("DELETE FROM post WHERE id = ?", (id,)) + db.commit() + return redirect(url_for("blog.index")) + + +Filepath: githubCode\examples\tutorial\flaskr\db.py: + +import sqlite3 + +import click +from flask import current_app +from flask import g + + +def get_db(): + """Connect to the application's configured database. The connection + is unique for each request and will be reused if this is called + again. + """ + if "db" not in g: + g.db = sqlite3.connect( + current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + + +def close_db(e=None): + """If this request connected to the database, close the + connection. + """ + db = g.pop("db", None) + + if db is not None: + db.close() + + +def init_db(): + """Clear existing data and create new tables.""" + db = get_db() + + with current_app.open_resource("schema.sql") as f: + db.executescript(f.read().decode("utf8")) + + +@click.command("init-db") +def init_db_command(): + """Clear existing data and create new tables.""" + init_db() + click.echo("Initialized the database.") + + +def init_app(app): + """Register database functions with the Flask app. This is called by + the application factory. + """ + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) + + +Filepath: githubCode\examples\tutorial\flaskr\__init__.py: + +import os + +from flask import Flask + + +def create_app(test_config=None): + """Create and configure an instance of the Flask application.""" + app = Flask(__name__, instance_relative_config=True) + app.config.from_mapping( + # a default secret that should be overridden by instance config + SECRET_KEY="dev", + # store the database in the instance folder + DATABASE=os.path.join(app.instance_path, "flaskr.sqlite"), + ) + + if test_config is None: + # load the instance config, if it exists, when not testing + app.config.from_pyfile("config.py", silent=True) + else: + # load the test config if passed in + app.config.update(test_config) + + # ensure the instance folder exists + try: + os.makedirs(app.instance_path) + except OSError: + pass + + @app.route("/hello") + def hello(): + return "Hello, World!" + + # register the database commands + from . import db + + db.init_app(app) + + # apply the blueprints to the app + from . import auth + from . import blog + + app.register_blueprint(auth.bp) + app.register_blueprint(blog.bp) + + # make url_for('index') == url_for('blog.index') + # in another app, you might define a separate main index here with + # app.route, while giving the blog blueprint a url_prefix, but for + # the tutorial the blog will be the main index + app.add_url_rule("/", endpoint="index") + + return app + + +Filepath: githubCode\examples\tutorial\tests\conftest.py: + +import os +import tempfile + +import pytest + +from flaskr import create_app +from flaskr.db import get_db +from flaskr.db import init_db + +# 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(): + """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) + + yield app + + # 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 runner(app): + """A test runner for the app's Click commands.""" + return app.test_cli_runner() + + +class AuthActions: + def __init__(self, client): + self._client = client + + def login(self, username="test", password="test"): + return self._client.post( + "/auth/login", data={"username": username, "password": password} + ) + + def logout(self): + return self._client.get("/auth/logout") + + +@pytest.fixture +def auth(client): + return AuthActions(client) + + +Filepath: githubCode\examples\tutorial\tests\test_auth.py: + +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 + + +Filepath: githubCode\examples\tutorial\tests\test_blog.py: + +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 + + +Filepath: githubCode\examples\tutorial\tests\test_db.py: + +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 + + +Filepath: githubCode\examples\tutorial\tests\test_factory.py: + +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!" + + +Filepath: githubCode\src\flask\app.py: + +from __future__ import annotations + +import collections.abc as cabc +import os +import sys +import typing as t +import weakref +from datetime import timedelta +from inspect import iscoroutinefunction +from itertools import chain +from types import TracebackType +from urllib.parse import quote as _url_quote + +import click +from werkzeug.datastructures import Headers +from werkzeug.datastructures import ImmutableDict +from werkzeug.exceptions import BadRequestKeyError +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import InternalServerError +from werkzeug.routing import BuildError +from werkzeug.routing import MapAdapter +from werkzeug.routing import RequestRedirect +from werkzeug.routing import RoutingException +from werkzeug.routing import Rule +from werkzeug.serving import is_running_from_reloader +from werkzeug.wrappers import Response as BaseResponse + +from . import cli +from . import typing as ft +from .ctx import AppContext +from .ctx import RequestContext +from .globals import _cv_app +from .globals import _cv_request +from .globals import current_app +from .globals import g +from .globals import request +from .globals import request_ctx +from .globals import session +from .helpers import get_debug_flag +from .helpers import get_flashed_messages +from .helpers import get_load_dotenv +from .helpers import send_from_directory +from .sansio.app import App +from .sansio.scaffold import _sentinel +from .sessions import SecureCookieSessionInterface +from .sessions import SessionInterface +from .signals import appcontext_tearing_down +from .signals import got_request_exception +from .signals import request_finished +from .signals import request_started +from .signals import request_tearing_down +from .templating import Environment +from .wrappers import Request +from .wrappers import Response + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIEnvironment + + from .testing import FlaskClient + from .testing import FlaskCliRunner + +T_shell_context_processor = t.TypeVar( + "T_shell_context_processor", bound=ft.ShellContextProcessorCallable +) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) +T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) +T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) + + +def _make_timedelta(value: timedelta | int | None) -> timedelta | None: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +class Flask(App): + """The flask object implements a WSGI application and acts as the central + object. It is passed the name of the module or package of the + application. Once it is created it will act as a central registry for + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 1.0 + The ``host_matching`` and ``static_host`` parameters were added. + + .. versionadded:: 1.0 + The ``subdomain_matching`` parameter was added. Subdomain + matching needs to be enabled manually now. Setting + :data:`SERVER_NAME` does not implicitly enable it. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: The folder with static files that is served at + ``static_url_path``. Relative to the application ``root_path`` + or an absolute path. Defaults to ``'static'``. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param host_matching: set ``url_map.host_matching`` attribute. + Defaults to False. + :param subdomain_matching: consider the subdomain relative to + :data:`SERVER_NAME` when matching routes. Defaults to False. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: The path to the root of the application files. + This should only be set manually when it can't be detected + automatically, such as for namespace packages. + """ + + default_config = ImmutableDict( + { + "DEBUG": None, + "TESTING": False, + "PROPAGATE_EXCEPTIONS": None, + "SECRET_KEY": None, + "PERMANENT_SESSION_LIFETIME": timedelta(days=31), + "USE_X_SENDFILE": False, + "SERVER_NAME": None, + "APPLICATION_ROOT": "/", + "SESSION_COOKIE_NAME": "session", + "SESSION_COOKIE_DOMAIN": None, + "SESSION_COOKIE_PATH": None, + "SESSION_COOKIE_HTTPONLY": True, + "SESSION_COOKIE_SECURE": False, + "SESSION_COOKIE_SAMESITE": None, + "SESSION_REFRESH_EACH_REQUEST": True, + "MAX_CONTENT_LENGTH": None, + "SEND_FILE_MAX_AGE_DEFAULT": None, + "TRAP_BAD_REQUEST_ERRORS": None, + "TRAP_HTTP_EXCEPTIONS": False, + "EXPLAIN_TEMPLATE_LOADING": False, + "PREFERRED_URL_SCHEME": "http", + "TEMPLATES_AUTO_RELOAD": None, + "MAX_COOKIE_SIZE": 4093, + } + ) + + #: The class that is used for request objects. See :class:`~flask.Request` + #: for more information. + request_class: type[Request] = Request + + #: The class that is used for response objects. See + #: :class:`~flask.Response` for more information. + response_class: type[Response] = Response + + #: the session interface to use. By default an instance of + #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. + #: + #: .. versionadded:: 0.8 + session_interface: SessionInterface = SecureCookieSessionInterface() + + def __init__( + self, + import_name: str, + static_url_path: str | None = None, + static_folder: str | os.PathLike[str] | None = "static", + static_host: str | None = None, + host_matching: bool = False, + subdomain_matching: bool = False, + template_folder: str | os.PathLike[str] | None = "templates", + instance_path: str | None = None, + instance_relative_config: bool = False, + root_path: str | None = None, + ): + super().__init__( + import_name=import_name, + static_url_path=static_url_path, + static_folder=static_folder, + static_host=static_host, + host_matching=host_matching, + subdomain_matching=subdomain_matching, + template_folder=template_folder, + instance_path=instance_path, + instance_relative_config=instance_relative_config, + root_path=root_path, + ) + + # Add a static route using the provided static_url_path, static_host, + # and static_folder if there is a configured static_folder. + # Note we do this without checking if static_folder exists. + # For one, it might be created while the server is running (e.g. during + # development). Also, Google App Engine stores static files somewhere + if self.has_static_folder: + assert ( + bool(static_host) == host_matching + ), "Invalid static_host/host_matching combination" + # Use a weakref to avoid creating a reference cycle between the app + # and the view function (see #3761). + self_ref = weakref.ref(self) + self.add_url_rule( + f"{self.static_url_path}/", + endpoint="static", + host=static_host, + view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950 + ) + + def get_send_file_max_age(self, filename: str | None) -> int | None: + """Used by :func:`send_file` to determine the ``max_age`` cache + value for a given file path if it wasn't passed. + + By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from + the configuration of :data:`~flask.current_app`. This defaults + to ``None``, which tells the browser to use conditional requests + instead of a timed cache, which is usually preferable. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionchanged:: 2.0 + The default configuration is ``None`` instead of 12 hours. + + .. versionadded:: 0.9 + """ + value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] + + if value is None: + return None + + if isinstance(value, timedelta): + return int(value.total_seconds()) + + return value # type: ignore[no-any-return] + + def send_static_file(self, filename: str) -> Response: + """The view function used to serve files from + :attr:`static_folder`. A route is automatically registered for + this view at :attr:`static_url_path` if :attr:`static_folder` is + set. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionadded:: 0.5 + + """ + if not self.has_static_folder: + raise RuntimeError("'static_folder' must be set to serve static_files.") + + # send_file only knows to call get_send_file_max_age on the app, + # call it here so it works for blueprints too. + max_age = self.get_send_file_max_age(filename) + return send_from_directory( + t.cast(str, self.static_folder), filename, max_age=max_age + ) + + def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + """Open a resource file relative to :attr:`root_path` for + reading. + + For example, if the file ``schema.sql`` is next to the file + ``app.py`` where the ``Flask`` app is defined, it can be opened + with: + + .. code-block:: python + + with app.open_resource("schema.sql") as f: + conn.executescript(f.read()) + + :param resource: Path to the resource relative to + :attr:`root_path`. + :param mode: Open the file in this mode. Only reading is + supported, valid values are "r" (or "rt") and "rb". + + Note this is a duplicate of the same method in the Flask + class. + + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading.") + + return open(os.path.join(self.root_path, resource), mode) + + def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + """Opens a resource from the application's instance folder + (:attr:`instance_path`). Otherwise works like + :meth:`open_resource`. Instance resources can also be opened for + writing. + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + :param mode: resource file opening mode, default is 'rb'. + """ + return open(os.path.join(self.instance_path, resource), mode) + + def create_jinja_environment(self) -> Environment: + """Create the Jinja environment based on :attr:`jinja_options` + and the various Jinja-related methods of the app. Changing + :attr:`jinja_options` after this will have no effect. Also adds + Flask-related globals and filters to the environment. + + .. versionchanged:: 0.11 + ``Environment.auto_reload`` set in accordance with + ``TEMPLATES_AUTO_RELOAD`` configuration option. + + .. versionadded:: 0.5 + """ + options = dict(self.jinja_options) + + if "autoescape" not in options: + options["autoescape"] = self.select_jinja_autoescape + + if "auto_reload" not in options: + auto_reload = self.config["TEMPLATES_AUTO_RELOAD"] + + if auto_reload is None: + auto_reload = self.debug + + options["auto_reload"] = auto_reload + + rv = self.jinja_environment(self, **options) + rv.globals.update( + url_for=self.url_for, + get_flashed_messages=get_flashed_messages, + config=self.config, + # request, session and g are normally added with the + # context processor for efficiency reasons but for imported + # templates we also want the proxies in there. + request=request, + session=session, + g=g, + ) + rv.policies["json.dumps_function"] = self.json.dumps + return rv + + def create_url_adapter(self, request: Request | None) -> MapAdapter | None: + """Creates a URL adapter for the given request. The URL adapter + is created at a point where the request context is not yet set + up so the request is passed explicitly. + + .. versionadded:: 0.6 + + .. versionchanged:: 0.9 + This can now also be called without a request object when the + URL adapter is created for the application context. + + .. versionchanged:: 1.0 + :data:`SERVER_NAME` no longer implicitly enables subdomain + matching. Use :attr:`subdomain_matching` instead. + """ + if request is not None: + # If subdomain matching is disabled (the default), use the + # default subdomain in all cases. This should be the default + # in Werkzeug but it currently does not have that feature. + if not self.subdomain_matching: + subdomain = self.url_map.default_subdomain or None + else: + subdomain = None + + return self.url_map.bind_to_environ( + request.environ, + server_name=self.config["SERVER_NAME"], + subdomain=subdomain, + ) + # We need at the very least the server name to be set for this + # to work. + if self.config["SERVER_NAME"] is not None: + return self.url_map.bind( + self.config["SERVER_NAME"], + script_name=self.config["APPLICATION_ROOT"], + url_scheme=self.config["PREFERRED_URL_SCHEME"], + ) + + return None + + def raise_routing_exception(self, request: Request) -> t.NoReturn: + """Intercept routing exceptions and possibly do something else. + + In debug mode, intercept a routing redirect and replace it with + an error if the body will be discarded. + + With modern Werkzeug this shouldn't occur, since it now uses a + 308 status which tells the browser to resend the method and + body. + + .. versionchanged:: 2.1 + Don't intercept 307 and 308 redirects. + + :meta private: + :internal: + """ + if ( + not self.debug + or not isinstance(request.routing_exception, RequestRedirect) + or request.routing_exception.code in {307, 308} + or request.method in {"GET", "HEAD", "OPTIONS"} + ): + raise request.routing_exception # type: ignore[misc] + + from .debughelpers import FormDataRoutingRedirect + + raise FormDataRoutingRedirect(request) + + def update_template_context(self, context: dict[str, t.Any]) -> None: + """Update the template context with some commonly used variables. + This injects request, session, config and g into the template + context as well as everything template context processors want + to inject. Note that the as of Flask 0.6, the original values + in the context will not be overridden if a context processor + decides to return a value with the same key. + + :param context: the context as a dictionary that is updated in place + to add extra variables. + """ + names: t.Iterable[str | None] = (None,) + + # A template may be rendered outside a request context. + if request: + names = chain(names, reversed(request.blueprints)) + + # The values passed to render_template take precedence. Keep a + # copy to re-apply after all context functions. + orig_ctx = context.copy() + + for name in names: + if name in self.template_context_processors: + for func in self.template_context_processors[name]: + context.update(self.ensure_sync(func)()) + + context.update(orig_ctx) + + def make_shell_context(self) -> dict[str, t.Any]: + """Returns the shell context for an interactive shell for this + application. This runs all the registered shell context + processors. + + .. versionadded:: 0.11 + """ + rv = {"app": self, "g": g} + for processor in self.shell_context_processors: + rv.update(processor()) + return rv + + def run( + self, + host: str | None = None, + port: int | None = None, + debug: bool | None = None, + load_dotenv: bool = True, + **options: t.Any, + ) -> None: + """Runs the application on a local development server. + + Do not use ``run()`` in a production setting. It is not intended to + meet security and performance requirements for a production server. + Instead, see :doc:`/deploying/index` for WSGI server recommendations. + + If the :attr:`debug` flag is set the server will automatically reload + for code changes and show a debugger in case an exception happened. + + If you want to run the application in debug mode, but disable the + code execution on the interactive debugger, you can pass + ``use_evalex=False`` as parameter. This will keep the debugger's + traceback screen active, but disable code execution. + + It is not recommended to use this function for development with + automatic reloading as this is badly supported. Instead you should + be using the :command:`flask` command line script's ``run`` support. + + .. admonition:: Keep in Mind + + Flask will suppress any server error with a generic error page + unless it is in debug mode. As such to enable just the + interactive debugger without the code reloading, you have to + invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``. + Setting ``use_debugger`` to ``True`` without being in debug mode + won't catch any exceptions because there won't be any to + catch. + + :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to + have the server available externally as well. Defaults to + ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable + if present. + :param port: the port of the webserver. Defaults to ``5000`` or the + port defined in the ``SERVER_NAME`` config variable if present. + :param debug: if given, enable or disable debug mode. See + :attr:`debug`. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param options: the options to be forwarded to the underlying Werkzeug + server. See :func:`werkzeug.serving.run_simple` for more + information. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment + variables from :file:`.env` and :file:`.flaskenv` files. + + The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`. + + Threaded mode is enabled by default. + + .. versionchanged:: 0.10 + The default port is now picked from the ``SERVER_NAME`` + variable. + """ + # Ignore this call so that it doesn't start another server if + # the 'flask run' command is used. + if os.environ.get("FLASK_RUN_FROM_CLI") == "true": + if not is_running_from_reloader(): + click.secho( + " * Ignoring a call to 'app.run()' that would block" + " the current 'flask' CLI command.\n" + " Only call 'app.run()' in an 'if __name__ ==" + ' "__main__"\' guard.', + fg="red", + ) + + return + + if get_load_dotenv(load_dotenv): + cli.load_dotenv() + + # if set, env var overrides existing value + if "FLASK_DEBUG" in os.environ: + self.debug = get_debug_flag() + + # debug passed to method overrides all other sources + if debug is not None: + self.debug = bool(debug) + + server_name = self.config.get("SERVER_NAME") + sn_host = sn_port = None + + if server_name: + sn_host, _, sn_port = server_name.partition(":") + + if not host: + if sn_host: + host = sn_host + else: + host = "127.0.0.1" + + if port or port == 0: + port = int(port) + elif sn_port: + port = int(sn_port) + else: + port = 5000 + + options.setdefault("use_reloader", self.debug) + options.setdefault("use_debugger", self.debug) + options.setdefault("threaded", True) + + cli.show_server_banner(self.debug, self.name) + + from werkzeug.serving import run_simple + + try: + run_simple(t.cast(str, host), port, self, **options) + finally: + # reset the first request information if the development server + # reset normally. This makes it possible to restart the server + # without reloader and that stuff from an interactive shell. + self._got_first_request = False + + def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient: + """Creates a test client for this application. For information + about unit testing head over to :doc:`/testing`. + + Note that if you are testing for assertions or exceptions in your + application code, you must set ``app.testing = True`` in order for the + exceptions to propagate to the test client. Otherwise, the exception + will be handled by the application (not visible to the test client) and + the only indication of an AssertionError or other exception will be a + 500 status code response to the test client. See the :attr:`testing` + attribute. For example:: + + app.testing = True + client = app.test_client() + + The test client can be used in a ``with`` block to defer the closing down + of the context until the end of the ``with`` block. This is useful if + you want to access the context locals for testing:: + + with app.test_client() as c: + rv = c.get('/?vodka=42') + assert request.args['vodka'] == '42' + + Additionally, you may pass optional keyword arguments that will then + be passed to the application's :attr:`test_client_class` constructor. + For example:: + + from flask.testing import FlaskClient + + class CustomClient(FlaskClient): + def __init__(self, *args, **kwargs): + self._authentication = kwargs.pop("authentication") + super(CustomClient,self).__init__( *args, **kwargs) + + app.test_client_class = CustomClient + client = app.test_client(authentication='Basic ....') + + See :class:`~flask.testing.FlaskClient` for more information. + + .. versionchanged:: 0.4 + added support for ``with`` block usage for the client. + + .. versionadded:: 0.7 + The `use_cookies` parameter was added as well as the ability + to override the client to be used by setting the + :attr:`test_client_class` attribute. + + .. versionchanged:: 0.11 + Added `**kwargs` to support passing additional keyword arguments to + the constructor of :attr:`test_client_class`. + """ + cls = self.test_client_class + if cls is None: + from .testing import FlaskClient as cls + return cls( # type: ignore + self, self.response_class, use_cookies=use_cookies, **kwargs + ) + + def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner: + """Create a CLI runner for testing CLI commands. + See :ref:`testing-cli`. + + Returns an instance of :attr:`test_cli_runner_class`, by default + :class:`~flask.testing.FlaskCliRunner`. The Flask app object is + passed as the first argument. + + .. versionadded:: 1.0 + """ + cls = self.test_cli_runner_class + + if cls is None: + from .testing import FlaskCliRunner as cls + + return cls(self, **kwargs) # type: ignore + + def handle_http_exception( + self, e: HTTPException + ) -> HTTPException | ft.ResponseReturnValue: + """Handles an HTTP exception. By default this will invoke the + registered error handlers and fall back to returning the + exception as response. + + .. versionchanged:: 1.0.3 + ``RoutingException``, used internally for actions such as + slash redirects during routing, is not passed to error + handlers. + + .. versionchanged:: 1.0 + Exceptions are looked up by code *and* by MRO, so + ``HTTPException`` subclasses can be handled with a catch-all + handler for the base ``HTTPException``. + + .. versionadded:: 0.3 + """ + # Proxy exceptions don't have error codes. We want to always return + # those unchanged as errors + if e.code is None: + return e + + # RoutingExceptions are used internally to trigger routing + # actions, such as slash redirects raising RequestRedirect. They + # are not raised or handled in user code. + if isinstance(e, RoutingException): + return e + + handler = self._find_error_handler(e, request.blueprints) + if handler is None: + return e + return self.ensure_sync(handler)(e) # type: ignore[no-any-return] + + def handle_user_exception( + self, e: Exception + ) -> HTTPException | ft.ResponseReturnValue: + """This method is called whenever an exception occurs that + should be handled. A special case is :class:`~werkzeug + .exceptions.HTTPException` which is forwarded to the + :meth:`handle_http_exception` method. This function will either + return a response value or reraise the exception with the same + traceback. + + .. versionchanged:: 1.0 + Key errors raised from request data like ``form`` show the + bad key in debug mode rather than a generic bad request + message. + + .. versionadded:: 0.7 + """ + if isinstance(e, BadRequestKeyError) and ( + self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"] + ): + e.show_exception = True + + if isinstance(e, HTTPException) and not self.trap_http_exception(e): + return self.handle_http_exception(e) + + handler = self._find_error_handler(e, request.blueprints) + + if handler is None: + raise + + return self.ensure_sync(handler)(e) # type: ignore[no-any-return] + + def handle_exception(self, e: Exception) -> Response: + """Handle an exception that did not have an error handler + associated with it, or that was raised from an error handler. + This always causes a 500 ``InternalServerError``. + + Always sends the :data:`got_request_exception` signal. + + If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug + mode, the error will be re-raised so that the debugger can + display it. Otherwise, the original exception is logged, and + an :exc:`~werkzeug.exceptions.InternalServerError` is returned. + + If an error handler is registered for ``InternalServerError`` or + ``500``, it will be used. For consistency, the handler will + always receive the ``InternalServerError``. The original + unhandled exception is available as ``e.original_exception``. + + .. versionchanged:: 1.1.0 + Always passes the ``InternalServerError`` instance to the + handler, setting ``original_exception`` to the unhandled + error. + + .. versionchanged:: 1.1.0 + ``after_request`` functions and other finalization is done + even for the default 500 response when there is no handler. + + .. versionadded:: 0.3 + """ + exc_info = sys.exc_info() + got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e) + propagate = self.config["PROPAGATE_EXCEPTIONS"] + + if propagate is None: + propagate = self.testing or self.debug + + if propagate: + # Re-raise if called with an active exception, otherwise + # raise the passed in exception. + if exc_info[1] is e: + raise + + raise e + + self.log_exception(exc_info) + server_error: InternalServerError | ft.ResponseReturnValue + server_error = InternalServerError(original_exception=e) + handler = self._find_error_handler(server_error, request.blueprints) + + if handler is not None: + server_error = self.ensure_sync(handler)(server_error) + + return self.finalize_request(server_error, from_error_handler=True) + + def log_exception( + self, + exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]), + ) -> None: + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error( + f"Exception on {request.path} [{request.method}]", exc_info=exc_info + ) + + def dispatch_request(self) -> ft.ResponseReturnValue: + """Does the request dispatching. Matches the URL and returns the + return value of the view or error handler. This does not have to + be a response object. In order to convert the return value to a + proper response object, call :func:`make_response`. + + .. versionchanged:: 0.7 + This no longer does the exception handling, this code was + moved to the new :meth:`full_dispatch_request`. + """ + req = request_ctx.request + if req.routing_exception is not None: + self.raise_routing_exception(req) + rule: Rule = req.url_rule # type: ignore[assignment] + # if we provide automatic options for this URL and the + # request came with the OPTIONS method, reply automatically + if ( + getattr(rule, "provide_automatic_options", False) + and req.method == "OPTIONS" + ): + return self.make_default_options_response() + # otherwise dispatch to the handler for that endpoint + view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment] + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] + + def full_dispatch_request(self) -> Response: + """Dispatches the request and on top of that performs request + pre and postprocessing as well as HTTP exception catching and + error handling. + + .. versionadded:: 0.7 + """ + self._got_first_request = True + + try: + request_started.send(self, _async_wrapper=self.ensure_sync) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() + except Exception as e: + rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request( + self, + rv: ft.ResponseReturnValue | HTTPException, + from_error_handler: bool = False, + ) -> Response: + """Given the return value from a view function this finalizes + the request by converting it into a response and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled, failures in + response processing will be logged and otherwise ignored. + + :internal: + """ + response = self.make_response(rv) + try: + response = self.process_response(response) + request_finished.send( + self, _async_wrapper=self.ensure_sync, response=response + ) + except Exception: + if not from_error_handler: + raise + self.logger.exception( + "Request finalizing failed with an error while handling an error" + ) + return response + + def make_default_options_response(self) -> Response: + """This method is called to create the default ``OPTIONS`` response. + This can be changed through subclassing to change the default + behavior of ``OPTIONS`` responses. + + .. versionadded:: 0.7 + """ + adapter = request_ctx.url_adapter + methods = adapter.allowed_methods() # type: ignore[union-attr] + rv = self.response_class() + rv.allow.update(methods) + return rv + + def ensure_sync(self, func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Ensure that the function is synchronous for WSGI workers. + Plain ``def`` functions are returned as-is. ``async def`` + functions are wrapped to run and wait for the response. + + Override this method to change how the app runs async views. + + .. versionadded:: 2.0 + """ + if iscoroutinefunction(func): + return self.async_to_sync(func) + + return func + + def async_to_sync( + self, func: t.Callable[..., t.Coroutine[t.Any, t.Any, t.Any]] + ) -> t.Callable[..., t.Any]: + """Return a sync function that will run the coroutine function. + + .. code-block:: python + + result = app.async_to_sync(func)(*args, **kwargs) + + Override this method to change how the app converts async code + to be synchronously callable. + + .. versionadded:: 2.0 + """ + try: + from asgiref.sync import async_to_sync as asgiref_async_to_sync + except ImportError: + raise RuntimeError( + "Install Flask with the 'async' extra in order to use async views." + ) from None + + return asgiref_async_to_sync(func) + + def url_for( + self, + /, + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, + ) -> str: + """Generate a URL to the given endpoint with the given values. + + This is called by :func:`flask.url_for`, and can be called + directly as well. + + An *endpoint* is the name of a URL rule, usually added with + :meth:`@app.route() `, and usually the same name as the + view function. A route defined in a :class:`~flask.Blueprint` + will prepend the blueprint's name separated by a ``.`` to the + endpoint. + + In some cases, such as email messages, you want URLs to include + the scheme and domain, like ``https://example.com/hello``. When + not in an active request, URLs will be external by default, but + this requires setting :data:`SERVER_NAME` so Flask knows what + domain to use. :data:`APPLICATION_ROOT` and + :data:`PREFERRED_URL_SCHEME` should also be configured as + needed. This config is only used when not in an active request. + + Functions can be decorated with :meth:`url_defaults` to modify + keyword arguments before the URL is built. + + If building fails for some reason, such as an unknown endpoint + or incorrect values, the app's :meth:`handle_url_build_error` + method is called. If that returns a string, that is returned, + otherwise a :exc:`~werkzeug.routing.BuildError` is raised. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it + is external. + :param _external: If given, prefer the URL to be internal + (False) or require it to be external (True). External URLs + include the scheme and domain. When not in an active + request, URLs are external by default. + :param values: Values to use for the variable parts of the URL + rule. Unknown keys are appended as query string arguments, + like ``?a=b&c=d``. + + .. versionadded:: 2.2 + Moved from ``flask.url_for``, which calls this method. + """ + req_ctx = _cv_request.get(None) + + if req_ctx is not None: + url_adapter = req_ctx.url_adapter + blueprint_name = req_ctx.request.blueprint + + # If the endpoint starts with "." and the request matches a + # blueprint, the endpoint is relative to the blueprint. + if endpoint[:1] == ".": + if blueprint_name is not None: + endpoint = f"{blueprint_name}{endpoint}" + else: + endpoint = endpoint[1:] + + # When in a request, generate a URL without scheme and + # domain by default, unless a scheme is given. + if _external is None: + _external = _scheme is not None + else: + app_ctx = _cv_app.get(None) + + # If called by helpers.url_for, an app context is active, + # use its url_adapter. Otherwise, app.url_for was called + # directly, build an adapter. + if app_ctx is not None: + url_adapter = app_ctx.url_adapter + else: + url_adapter = self.create_url_adapter(None) + + if url_adapter is None: + raise RuntimeError( + "Unable to build URLs outside an active request" + " without 'SERVER_NAME' configured. Also configure" + " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as" + " needed." + ) + + # When outside a request, generate a URL with scheme and + # domain by default. + if _external is None: + _external = True + + # It is an error to set _scheme when _external=False, in order + # to avoid accidental insecure URLs. + if _scheme is not None and not _external: + raise ValueError("When specifying '_scheme', '_external' must be True.") + + self.inject_url_defaults(endpoint, values) + + try: + rv = url_adapter.build( # type: ignore[union-attr] + endpoint, + values, + method=_method, + url_scheme=_scheme, + force_external=_external, + ) + except BuildError as error: + values.update( + _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external + ) + return self.handle_url_build_error(error, endpoint, values) + + if _anchor is not None: + _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@") + rv = f"{rv}#{_anchor}" + + return rv + + def make_response(self, rv: ft.ResponseReturnValue) -> Response: + """Convert the return value from a view function to an instance of + :attr:`response_class`. + + :param rv: the return value from the view function. The view function + must return a response. Returning ``None``, or the view ending + without returning, is not allowed. The following types are allowed + for ``view_rv``: + + ``str`` + A response object is created with the string encoded to UTF-8 + as the body. + + ``bytes`` + A response object is created with the bytes as the body. + + ``dict`` + A dictionary that will be jsonify'd before being returned. + + ``list`` + A list that will be jsonify'd before being returned. + + ``generator`` or ``iterator`` + A generator that returns ``str`` or ``bytes`` to be + streamed as the response. + + ``tuple`` + Either ``(body, status, headers)``, ``(body, status)``, or + ``(body, headers)``, where ``body`` is any of the other types + allowed here, ``status`` is a string or an integer, and + ``headers`` is a dictionary or a list of ``(key, value)`` + tuples. If ``body`` is a :attr:`response_class` instance, + ``status`` overwrites the exiting value and ``headers`` are + extended. + + :attr:`response_class` + The object is returned unchanged. + + other :class:`~werkzeug.wrappers.Response` class + The object is coerced to :attr:`response_class`. + + :func:`callable` + The function is called as a WSGI application. The result is + used to create a response object. + + .. versionchanged:: 2.2 + A generator will be converted to a streaming response. + A list will be converted to a JSON response. + + .. versionchanged:: 1.1 + A dict will be converted to a JSON response. + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. + """ + + status = headers = None + + # unpack tuple returns + if isinstance(rv, tuple): + len_rv = len(rv) + + # a 3-tuple is unpacked directly + if len_rv == 3: + rv, status, headers = rv # type: ignore[misc] + # decide if a 2-tuple has status or headers + elif len_rv == 2: + if isinstance(rv[1], (Headers, dict, tuple, list)): + rv, headers = rv + else: + rv, status = rv # type: ignore[assignment,misc] + # other sized tuples are not allowed + else: + raise TypeError( + "The view function did not return a valid response tuple." + " The tuple must have the form (body, status, headers)," + " (body, status), or (body, headers)." + ) + + # the body must not be None + if rv is None: + raise TypeError( + f"The view function for {request.endpoint!r} did not" + " return a valid response. The function either returned" + " None or ended without a return statement." + ) + + # make sure the body is an instance of the response class + if not isinstance(rv, self.response_class): + if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, cabc.Iterator): + # let the response class set the status and headers instead of + # waiting to do it manually, so that the class can handle any + # special logic + rv = self.response_class( + rv, + status=status, + headers=headers, # type: ignore[arg-type] + ) + status = headers = None + elif isinstance(rv, (dict, list)): + rv = self.json.response(rv) + elif isinstance(rv, BaseResponse) or callable(rv): + # evaluate a WSGI callable, or coerce a different response + # class to the correct type + try: + rv = self.response_class.force_type( + rv, # type: ignore[arg-type] + request.environ, + ) + except TypeError as e: + raise TypeError( + f"{e}\nThe view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it" + f" was a {type(rv).__name__}." + ).with_traceback(sys.exc_info()[2]) from None + else: + raise TypeError( + "The view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it was a" + f" {type(rv).__name__}." + ) + + rv = t.cast(Response, rv) + # prefer the status if it was provided + if status is not None: + if isinstance(status, (str, bytes, bytearray)): + rv.status = status + else: + rv.status_code = status + + # extend existing headers with provided headers + if headers: + rv.headers.update(headers) # type: ignore[arg-type] + + return rv + + def preprocess_request(self) -> ft.ResponseReturnValue | None: + """Called before the request is dispatched. Calls + :attr:`url_value_preprocessors` registered with the app and the + current blueprint (if any). Then calls :attr:`before_request_funcs` + registered with the app and the blueprint. + + If any :meth:`before_request` handler returns a non-None value, the + value is handled as if it was the return value from the view, and + further request handling is stopped. + """ + names = (None, *reversed(request.blueprints)) + + for name in names: + if name in self.url_value_preprocessors: + for url_func in self.url_value_preprocessors[name]: + url_func(request.endpoint, request.view_args) + + for name in names: + if name in self.before_request_funcs: + for before_func in self.before_request_funcs[name]: + rv = self.ensure_sync(before_func)() + + if rv is not None: + return rv # type: ignore[no-any-return] + + return None + + def process_response(self, response: Response) -> Response: + """Can be overridden in order to modify the response object + before it's sent to the WSGI server. By default this will + call all the :meth:`after_request` decorated functions. + + .. versionchanged:: 0.5 + As of Flask 0.5 the functions registered for after request + execution are called in reverse order of registration. + + :param response: a :attr:`response_class` object. + :return: a new response object or the same, has to be an + instance of :attr:`response_class`. + """ + ctx = request_ctx._get_current_object() # type: ignore[attr-defined] + + for func in ctx._after_request_functions: + response = self.ensure_sync(func)(response) + + for name in chain(request.blueprints, (None,)): + if name in self.after_request_funcs: + for func in reversed(self.after_request_funcs[name]): + response = self.ensure_sync(func)(response) + + if not self.session_interface.is_null_session(ctx.session): + self.session_interface.save_session(self, ctx.session, response) + + return response + + def do_teardown_request( + self, + exc: BaseException | None = _sentinel, # type: ignore[assignment] + ) -> None: + """Called after the request is dispatched and the response is + returned, right before the request context is popped. + + This calls all functions decorated with + :meth:`teardown_request`, and :meth:`Blueprint.teardown_request` + if a blueprint handled the request. Finally, the + :data:`request_tearing_down` signal is sent. + + This is called by + :meth:`RequestContext.pop() `, + which may be delayed during testing to maintain access to + resources. + + :param exc: An unhandled exception raised while dispatching the + request. Detected from the current exception information if + not passed. Passed to each teardown function. + + .. versionchanged:: 0.9 + Added the ``exc`` argument. + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for name in chain(request.blueprints, (None,)): + if name in self.teardown_request_funcs: + for func in reversed(self.teardown_request_funcs[name]): + self.ensure_sync(func)(exc) + + request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def do_teardown_appcontext( + self, + exc: BaseException | None = _sentinel, # type: ignore[assignment] + ) -> None: + """Called right before the application context is popped. + + When handling a request, the application context is popped + after the request context. See :meth:`do_teardown_request`. + + This calls all functions decorated with + :meth:`teardown_appcontext`. Then the + :data:`appcontext_tearing_down` signal is sent. + + This is called by + :meth:`AppContext.pop() `. + + .. versionadded:: 0.9 + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for func in reversed(self.teardown_appcontext_funcs): + self.ensure_sync(func)(exc) + + appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def app_context(self) -> AppContext: + """Create an :class:`~flask.ctx.AppContext`. Use as a ``with`` + block to push the context, which will make :data:`current_app` + point at this application. + + An application context is automatically pushed by + :meth:`RequestContext.push() ` + when handling a request, and when running a CLI command. Use + this to manually create a context outside of these situations. + + :: + + with app.app_context(): + init_db() + + See :doc:`/appcontext`. + + .. versionadded:: 0.9 + """ + return AppContext(self) + + def request_context(self, environ: WSGIEnvironment) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` representing a + WSGI environment. Use a ``with`` block to push the context, + which will make :data:`request` point at this request. + + See :doc:`/reqcontext`. + + Typically you should not call this from your own code. A request + context is automatically pushed by the :meth:`wsgi_app` when + handling a request. Use :meth:`test_request_context` to create + an environment and context instead of this method. + + :param environ: a WSGI environment + """ + return RequestContext(self, environ) + + def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` for a WSGI + environment created from the given values. This is mostly useful + during testing, where you may want to run a function that uses + request data without dispatching a full request. + + See :doc:`/reqcontext`. + + Use a ``with`` block to push the context, which will make + :data:`request` point at the request for the created + environment. :: + + with app.test_request_context(...): + generate_report() + + When using the shell, it may be easier to push and pop the + context manually to avoid indentation. :: + + ctx = app.test_request_context(...) + ctx.push() + ... + ctx.pop() + + Takes the same arguments as Werkzeug's + :class:`~werkzeug.test.EnvironBuilder`, with some defaults from + the application. See the linked Werkzeug docs for most of the + available arguments. Flask-specific behavior is listed here. + + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to + :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param data: The request body, either as a string or a dict of + form keys and values. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + from .testing import EnvironBuilder + + builder = EnvironBuilder(self, *args, **kwargs) + + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() + + def wsgi_app( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> cabc.Iterable[bytes]: + """The actual WSGI application. This is not implemented in + :meth:`__call__` so that middlewares can be applied without + losing a reference to the app object. Instead of doing this:: + + app = MyMiddleware(app) + + It's a better idea to do this instead:: + + app.wsgi_app = MyMiddleware(app.wsgi_app) + + Then you still have the original application object around and + can continue to call methods on it. + + .. versionchanged:: 0.7 + Teardown events for the request and app contexts are called + even if an unhandled error occurs. Other events may not be + called depending on when an error occurs during dispatch. + See :ref:`callbacks-and-errors`. + + :param environ: A WSGI environment. + :param start_response: A callable accepting a status code, + a list of headers, and an optional exception context to + start the response. + """ + ctx = self.request_context(environ) + error: BaseException | None = None + try: + try: + ctx.push() + response = self.full_dispatch_request() + except Exception as e: + error = e + response = self.handle_exception(e) + except: # noqa: B001 + error = sys.exc_info()[1] + raise + return response(environ, start_response) + finally: + if "werkzeug.debug.preserve_context" in environ: + environ["werkzeug.debug.preserve_context"](_cv_app.get()) + environ["werkzeug.debug.preserve_context"](_cv_request.get()) + + if error is not None and self.should_ignore_error(error): + error = None + + ctx.pop(error) + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> cabc.Iterable[bytes]: + """The WSGI server calls the Flask application object as the + WSGI application. This calls :meth:`wsgi_app`, which can be + wrapped to apply middleware. + """ + return self.wsgi_app(environ, start_response) + + +Filepath: githubCode\src\flask\blueprints.py: + +from __future__ import annotations + +import os +import typing as t +from datetime import timedelta + +from .globals import current_app +from .helpers import send_from_directory +from .sansio.blueprints import Blueprint as SansioBlueprint +from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa + +if t.TYPE_CHECKING: # pragma: no cover + from .wrappers import Response + + +class Blueprint(SansioBlueprint): + def get_send_file_max_age(self, filename: str | None) -> int | None: + """Used by :func:`send_file` to determine the ``max_age`` cache + value for a given file path if it wasn't passed. + + By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from + the configuration of :data:`~flask.current_app`. This defaults + to ``None``, which tells the browser to use conditional requests + instead of a timed cache, which is usually preferable. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionchanged:: 2.0 + The default configuration is ``None`` instead of 12 hours. + + .. versionadded:: 0.9 + """ + value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] + + if value is None: + return None + + if isinstance(value, timedelta): + return int(value.total_seconds()) + + return value # type: ignore[no-any-return] + + def send_static_file(self, filename: str) -> Response: + """The view function used to serve files from + :attr:`static_folder`. A route is automatically registered for + this view at :attr:`static_url_path` if :attr:`static_folder` is + set. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionadded:: 0.5 + + """ + if not self.has_static_folder: + raise RuntimeError("'static_folder' must be set to serve static_files.") + + # send_file only knows to call get_send_file_max_age on the app, + # call it here so it works for blueprints too. + max_age = self.get_send_file_max_age(filename) + return send_from_directory( + t.cast(str, self.static_folder), filename, max_age=max_age + ) + + def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + """Open a resource file relative to :attr:`root_path` for + reading. + + For example, if the file ``schema.sql`` is next to the file + ``app.py`` where the ``Flask`` app is defined, it can be opened + with: + + .. code-block:: python + + with app.open_resource("schema.sql") as f: + conn.executescript(f.read()) + + :param resource: Path to the resource relative to + :attr:`root_path`. + :param mode: Open the file in this mode. Only reading is + supported, valid values are "r" (or "rt") and "rb". + + Note this is a duplicate of the same method in the Flask + class. + + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading.") + + return open(os.path.join(self.root_path, resource), mode) + + +Filepath: githubCode\src\flask\cli.py: + +from __future__ import annotations + +import ast +import collections.abc as cabc +import importlib.metadata +import inspect +import os +import platform +import re +import sys +import traceback +import typing as t +from functools import update_wrapper +from operator import itemgetter +from types import ModuleType + +import click +from click.core import ParameterSource +from werkzeug import run_simple +from werkzeug.serving import is_running_from_reloader +from werkzeug.utils import import_string + +from .globals import current_app +from .helpers import get_debug_flag +from .helpers import get_load_dotenv + +if t.TYPE_CHECKING: + import ssl + + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + from .app import Flask + + +class NoAppException(click.UsageError): + """Raised if an application cannot be found or loaded.""" + + +def find_best_app(module: ModuleType) -> Flask: + """Given a module instance this tries to find the best possible + application in the module or raises an exception. + """ + from . import Flask + + # Search for the most common names first. + for attr_name in ("app", "application"): + app = getattr(module, attr_name, None) + + if isinstance(app, Flask): + return app + + # Otherwise find the only object that is a Flask instance. + matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] + + if len(matches) == 1: + return matches[0] + elif len(matches) > 1: + raise NoAppException( + "Detected multiple Flask applications in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify the correct one." + ) + + # Search for app factory functions. + for attr_name in ("create_app", "make_app"): + app_factory = getattr(module, attr_name, None) + + if inspect.isfunction(app_factory): + try: + app = app_factory() + + if isinstance(app, Flask): + return app + except TypeError as e: + if not _called_with_wrong_args(app_factory): + raise + + raise NoAppException( + f"Detected factory '{attr_name}' in module '{module.__name__}'," + " but could not call it without arguments. Use" + f" '{module.__name__}:{attr_name}(args)'" + " to specify arguments." + ) from e + + raise NoAppException( + "Failed to find Flask application or factory in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify one." + ) + + +def _called_with_wrong_args(f: t.Callable[..., Flask]) -> bool: + """Check whether calling a function raised a ``TypeError`` because + the call failed or because something in the factory raised the + error. + + :param f: The function that was called. + :return: ``True`` if the call failed. + """ + tb = sys.exc_info()[2] + + try: + while tb is not None: + if tb.tb_frame.f_code is f.__code__: + # In the function, it was called successfully. + return False + + tb = tb.tb_next + + # Didn't reach the function. + return True + finally: + # Delete tb to break a circular reference. + # https://docs.python.org/2/library/sys.html#sys.exc_info + del tb + + +def find_app_by_string(module: ModuleType, app_name: str) -> Flask: + """Check if the given string is a variable name or a function. Call + a function to get the app instance, or return the variable directly. + """ + from . import Flask + + # Parse app_name as a single expression to determine if it's a valid + # attribute name or function call. + try: + expr = ast.parse(app_name.strip(), mode="eval").body + except SyntaxError: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) from None + + if isinstance(expr, ast.Name): + name = expr.id + args = [] + kwargs = {} + elif isinstance(expr, ast.Call): + # Ensure the function name is an attribute name only. + if not isinstance(expr.func, ast.Name): + raise NoAppException( + f"Function reference must be a simple name: {app_name!r}." + ) + + name = expr.func.id + + # Parse the positional and keyword arguments as literals. + try: + args = [ast.literal_eval(arg) for arg in expr.args] + kwargs = { + kw.arg: ast.literal_eval(kw.value) + for kw in expr.keywords + if kw.arg is not None + } + except ValueError: + # literal_eval gives cryptic error messages, show a generic + # message with the full expression instead. + raise NoAppException( + f"Failed to parse arguments as literal values: {app_name!r}." + ) from None + else: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) + + try: + attr = getattr(module, name) + except AttributeError as e: + raise NoAppException( + f"Failed to find attribute {name!r} in {module.__name__!r}." + ) from e + + # If the attribute is a function, call it with any args and kwargs + # to get the real application. + if inspect.isfunction(attr): + try: + app = attr(*args, **kwargs) + except TypeError as e: + if not _called_with_wrong_args(attr): + raise + + raise NoAppException( + f"The factory {app_name!r} in module" + f" {module.__name__!r} could not be called with the" + " specified arguments." + ) from e + else: + app = attr + + if isinstance(app, Flask): + return app + + raise NoAppException( + "A valid Flask application was not obtained from" + f" '{module.__name__}:{app_name}'." + ) + + +def prepare_import(path: str) -> str: + """Given a filename this will try to calculate the python path, add it + to the search path and return the actual module name that is expected. + """ + path = os.path.realpath(path) + + fname, ext = os.path.splitext(path) + if ext == ".py": + path = fname + + if os.path.basename(path) == "__init__": + path = os.path.dirname(path) + + module_name = [] + + # move up until outside package structure (no __init__.py) + while True: + path, name = os.path.split(path) + module_name.append(name) + + if not os.path.exists(os.path.join(path, "__init__.py")): + break + + if sys.path[0] != path: + sys.path.insert(0, path) + + return ".".join(module_name[::-1]) + + +@t.overload +def locate_app( + module_name: str, app_name: str | None, raise_if_not_found: t.Literal[True] = True +) -> Flask: + ... + + +@t.overload +def locate_app( + module_name: str, app_name: str | None, raise_if_not_found: t.Literal[False] = ... +) -> Flask | None: + ... + + +def locate_app( + module_name: str, app_name: str | None, raise_if_not_found: bool = True +) -> Flask | None: + try: + __import__(module_name) + except ImportError: + # Reraise the ImportError if it occurred within the imported module. + # Determine this by checking whether the trace has a depth > 1. + if sys.exc_info()[2].tb_next: # type: ignore[union-attr] + raise NoAppException( + f"While importing {module_name!r}, an ImportError was" + f" raised:\n\n{traceback.format_exc()}" + ) from None + elif raise_if_not_found: + raise NoAppException(f"Could not import {module_name!r}.") from None + else: + return None + + module = sys.modules[module_name] + + if app_name is None: + return find_best_app(module) + else: + return find_app_by_string(module, app_name) + + +def get_version(ctx: click.Context, param: click.Parameter, value: t.Any) -> None: + if not value or ctx.resilient_parsing: + return + + flask_version = importlib.metadata.version("flask") + werkzeug_version = importlib.metadata.version("werkzeug") + + click.echo( + f"Python {platform.python_version()}\n" + f"Flask {flask_version}\n" + f"Werkzeug {werkzeug_version}", + color=ctx.color, + ) + ctx.exit() + + +version_option = click.Option( + ["--version"], + help="Show the Flask version.", + expose_value=False, + callback=get_version, + is_flag=True, + is_eager=True, +) + + +class ScriptInfo: + """Helper object to deal with Flask applications. This is usually not + necessary to interface with as it's used internally in the dispatching + to click. In future versions of Flask this object will most likely play + a bigger role. Typically it's created automatically by the + :class:`FlaskGroup` but you can also manually create it and pass it + onwards as click object. + """ + + def __init__( + self, + app_import_path: str | None = None, + create_app: t.Callable[..., Flask] | None = None, + set_debug_flag: bool = True, + ) -> None: + #: Optionally the import path for the Flask application. + self.app_import_path = app_import_path + #: Optionally a function that is passed the script info to create + #: the instance of the application. + self.create_app = create_app + #: A dictionary with arbitrary data that can be associated with + #: this script info. + self.data: dict[t.Any, t.Any] = {} + self.set_debug_flag = set_debug_flag + self._loaded_app: Flask | None = None + + def load_app(self) -> Flask: + """Loads the Flask app (if not yet loaded) and returns it. Calling + this multiple times will just result in the already loaded app to + be returned. + """ + if self._loaded_app is not None: + return self._loaded_app + + if self.create_app is not None: + app: Flask | None = self.create_app() + else: + if self.app_import_path: + path, name = ( + re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None] + )[:2] + import_name = prepare_import(path) + app = locate_app(import_name, name) + else: + for path in ("wsgi.py", "app.py"): + import_name = prepare_import(path) + app = locate_app(import_name, None, raise_if_not_found=False) + + if app is not None: + break + + if app is None: + raise NoAppException( + "Could not locate a Flask application. Use the" + " 'flask --app' option, 'FLASK_APP' environment" + " variable, or a 'wsgi.py' or 'app.py' file in the" + " current directory." + ) + + if self.set_debug_flag: + # Update the app's debug flag through the descriptor so that + # other values repopulate as well. + app.debug = get_debug_flag() + + self._loaded_app = app + return app + + +pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True) + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def with_appcontext(f: F) -> F: + """Wraps a callback so that it's guaranteed to be executed with the + script's application context. + + Custom commands (and their options) registered under ``app.cli`` or + ``blueprint.cli`` will always have an app context available, this + decorator is not required in that case. + + .. versionchanged:: 2.2 + The app context is active for subcommands as well as the + decorated callback. The app context is always available to + ``app.cli`` command and parameter callbacks. + """ + + @click.pass_context + def decorator(ctx: click.Context, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + if not current_app: + app = ctx.ensure_object(ScriptInfo).load_app() + ctx.with_resource(app.app_context()) + + return ctx.invoke(f, *args, **kwargs) + + return update_wrapper(decorator, f) # type: ignore[return-value] + + +class AppGroup(click.Group): + """This works similar to a regular click :class:`~click.Group` but it + changes the behavior of the :meth:`command` decorator so that it + automatically wraps the functions in :func:`with_appcontext`. + + Not to be confused with :class:`FlaskGroup`. + """ + + def command( # type: ignore[override] + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], click.Command]: + """This works exactly like the method of the same name on a regular + :class:`click.Group` but it wraps callbacks in :func:`with_appcontext` + unless it's disabled by passing ``with_appcontext=False``. + """ + wrap_for_ctx = kwargs.pop("with_appcontext", True) + + def decorator(f: t.Callable[..., t.Any]) -> click.Command: + if wrap_for_ctx: + f = with_appcontext(f) + return super(AppGroup, self).command(*args, **kwargs)(f) # type: ignore[no-any-return] + + return decorator + + def group( # type: ignore[override] + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], click.Group]: + """This works exactly like the method of the same name on a regular + :class:`click.Group` but it defaults the group class to + :class:`AppGroup`. + """ + kwargs.setdefault("cls", AppGroup) + return super().group(*args, **kwargs) # type: ignore[no-any-return] + + +def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None: + if value is None: + return None + + info = ctx.ensure_object(ScriptInfo) + info.app_import_path = value + return value + + +# This option is eager so the app will be available if --help is given. +# --help is also eager, so --app must be before it in the param list. +# no_args_is_help bypasses eager processing, so this option must be +# processed manually in that case to ensure FLASK_APP gets picked up. +_app_option = click.Option( + ["-A", "--app"], + metavar="IMPORT", + help=( + "The Flask application or factory function to load, in the form 'module:name'." + " Module can be a dotted import or file path. Name is not required if it is" + " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to" + " pass arguments." + ), + is_eager=True, + expose_value=False, + callback=_set_app, +) + + +def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None: + # If the flag isn't provided, it will default to False. Don't use + # that, let debug be set by env in that case. + source = ctx.get_parameter_source(param.name) # type: ignore[arg-type] + + if source is not None and source in ( + ParameterSource.DEFAULT, + ParameterSource.DEFAULT_MAP, + ): + return None + + # Set with env var instead of ScriptInfo.load so that it can be + # accessed early during a factory function. + os.environ["FLASK_DEBUG"] = "1" if value else "0" + return value + + +_debug_option = click.Option( + ["--debug/--no-debug"], + help="Set debug mode.", + expose_value=False, + callback=_set_debug, +) + + +def _env_file_callback( + ctx: click.Context, param: click.Option, value: str | None +) -> str | None: + if value is None: + return None + + import importlib + + try: + importlib.import_module("dotenv") + except ImportError: + raise click.BadParameter( + "python-dotenv must be installed to load an env file.", + ctx=ctx, + param=param, + ) from None + + # Don't check FLASK_SKIP_DOTENV, that only disables automatically + # loading .env and .flaskenv files. + load_dotenv(value) + return value + + +# This option is eager so env vars are loaded as early as possible to be +# used by other options. +_env_file_option = click.Option( + ["-e", "--env-file"], + type=click.Path(exists=True, dir_okay=False), + help="Load environment variables from this file. python-dotenv must be installed.", + is_eager=True, + expose_value=False, + callback=_env_file_callback, +) + + +class FlaskGroup(AppGroup): + """Special subclass of the :class:`AppGroup` group that supports + loading more commands from the configured Flask app. Normally a + developer does not have to interface with this class but there are + some very advanced use cases for which it makes sense to create an + instance of this. see :ref:`custom-scripts`. + + :param add_default_commands: if this is True then the default run and + shell commands will be added. + :param add_version_option: adds the ``--version`` option. + :param create_app: an optional callback that is passed the script info and + returns the loaded app. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param set_debug_flag: Set the app's debug flag. + + .. versionchanged:: 2.2 + Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options. + + .. versionchanged:: 2.2 + An app context is pushed when running ``app.cli`` commands, so + ``@with_appcontext`` is no longer required for those commands. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment variables + from :file:`.env` and :file:`.flaskenv` files. + """ + + def __init__( + self, + add_default_commands: bool = True, + create_app: t.Callable[..., Flask] | None = None, + add_version_option: bool = True, + load_dotenv: bool = True, + set_debug_flag: bool = True, + **extra: t.Any, + ) -> None: + params = list(extra.pop("params", None) or ()) + # Processing is done with option callbacks instead of a group + # callback. This allows users to make a custom group callback + # without losing the behavior. --env-file must come first so + # that it is eagerly evaluated before --app. + params.extend((_env_file_option, _app_option, _debug_option)) + + if add_version_option: + params.append(version_option) + + if "context_settings" not in extra: + extra["context_settings"] = {} + + extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK") + + super().__init__(params=params, **extra) + + self.create_app = create_app + self.load_dotenv = load_dotenv + self.set_debug_flag = set_debug_flag + + if add_default_commands: + self.add_command(run_command) + self.add_command(shell_command) + self.add_command(routes_command) + + self._loaded_plugin_commands = False + + def _load_plugin_commands(self) -> None: + if self._loaded_plugin_commands: + return + + if sys.version_info >= (3, 10): + from importlib import metadata + else: + # Use a backport on Python < 3.10. We technically have + # importlib.metadata on 3.8+, but the API changed in 3.10, + # so use the backport for consistency. + import importlib_metadata as metadata + + for ep in metadata.entry_points(group="flask.commands"): + self.add_command(ep.load(), ep.name) + + self._loaded_plugin_commands = True + + def get_command(self, ctx: click.Context, name: str) -> click.Command | None: + self._load_plugin_commands() + # Look up built-in and plugin commands, which should be + # available even if the app fails to load. + rv = super().get_command(ctx, name) + + if rv is not None: + return rv + + info = ctx.ensure_object(ScriptInfo) + + # Look up commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + app = info.load_app() + except NoAppException as e: + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + return None + + # Push an app context for the loaded app unless it is already + # active somehow. This makes the context available to parameter + # and command callbacks without needing @with_appcontext. + if not current_app or current_app._get_current_object() is not app: # type: ignore[attr-defined] + ctx.with_resource(app.app_context()) + + return app.cli.get_command(ctx, name) + + def list_commands(self, ctx: click.Context) -> list[str]: + self._load_plugin_commands() + # Start with the built-in and plugin commands. + rv = set(super().list_commands(ctx)) + info = ctx.ensure_object(ScriptInfo) + + # Add commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + rv.update(info.load_app().cli.list_commands(ctx)) + except NoAppException as e: + # When an app couldn't be loaded, show the error message + # without the traceback. + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + except Exception: + # When any other errors occurred during loading, show the + # full traceback. + click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") + + return sorted(rv) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: click.Context | None = None, + **extra: t.Any, + ) -> click.Context: + # Set a flag to tell app.run to become a no-op. If app.run was + # not in a __name__ == __main__ guard, it would start the server + # when importing, blocking whatever command is being called. + os.environ["FLASK_RUN_FROM_CLI"] = "true" + + # Attempt to load .env and .flask env files. The --env-file + # option can cause another file to be loaded. + if get_load_dotenv(self.load_dotenv): + load_dotenv() + + if "obj" not in extra and "obj" not in self.context_settings: + extra["obj"] = ScriptInfo( + create_app=self.create_app, set_debug_flag=self.set_debug_flag + ) + + return super().make_context(info_name, args, parent=parent, **extra) + + def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help: + # Attempt to load --env-file and --app early in case they + # were given as env vars. Otherwise no_args_is_help will not + # see commands from app.cli. + _env_file_option.handle_parse_result(ctx, {}, []) + _app_option.handle_parse_result(ctx, {}, []) + + return super().parse_args(ctx, args) + + +def _path_is_ancestor(path: str, other: str) -> bool: + """Take ``other`` and remove the length of ``path`` from it. Then join it + to ``path``. If it is the original value, ``path`` is an ancestor of + ``other``.""" + return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other + + +def load_dotenv(path: str | os.PathLike[str] | None = None) -> bool: + """Load "dotenv" files in order of precedence to set environment variables. + + If an env var is already set it is not overwritten, so earlier files in the + list are preferred over later files. + + This is a no-op if `python-dotenv`_ is not installed. + + .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme + + :param path: Load the file at this location instead of searching. + :return: ``True`` if a file was loaded. + + .. versionchanged:: 2.0 + The current directory is not changed to the location of the + loaded file. + + .. versionchanged:: 2.0 + When loading the env files, set the default encoding to UTF-8. + + .. versionchanged:: 1.1.0 + Returns ``False`` when python-dotenv is not installed, or when + the given path isn't a file. + + .. versionadded:: 1.0 + """ + try: + import dotenv + except ImportError: + if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): + click.secho( + " * Tip: There are .env or .flaskenv files present." + ' Do "pip install python-dotenv" to use them.', + fg="yellow", + err=True, + ) + + return False + + # Always return after attempting to load a given path, don't load + # the default files. + if path is not None: + if os.path.isfile(path): + return dotenv.load_dotenv(path, encoding="utf-8") + + return False + + loaded = False + + for name in (".env", ".flaskenv"): + path = dotenv.find_dotenv(name, usecwd=True) + + if not path: + continue + + dotenv.load_dotenv(path, encoding="utf-8") + loaded = True + + return loaded # True if at least one file was located and loaded. + + +def show_server_banner(debug: bool, app_import_path: str | None) -> None: + """Show extra startup messages the first time the server is run, + ignoring the reloader. + """ + if is_running_from_reloader(): + return + + if app_import_path is not None: + click.echo(f" * Serving Flask app '{app_import_path}'") + + if debug is not None: + click.echo(f" * Debug mode: {'on' if debug else 'off'}") + + +class CertParamType(click.ParamType): + """Click option type for the ``--cert`` option. Allows either an + existing file, the string ``'adhoc'``, or an import for a + :class:`~ssl.SSLContext` object. + """ + + name = "path" + + def __init__(self) -> None: + self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) + + def convert( + self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None + ) -> t.Any: + try: + import ssl + except ImportError: + raise click.BadParameter( + 'Using "--cert" requires Python to be compiled with SSL support.', + ctx, + param, + ) from None + + try: + return self.path_type(value, param, ctx) + except click.BadParameter: + value = click.STRING(value, param, ctx).lower() + + if value == "adhoc": + try: + import cryptography # noqa: F401 + except ImportError: + raise click.BadParameter( + "Using ad-hoc certificates requires the cryptography library.", + ctx, + param, + ) from None + + return value + + obj = import_string(value, silent=True) + + if isinstance(obj, ssl.SSLContext): + return obj + + raise + + +def _validate_key(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any: + """The ``--key`` option must be specified when ``--cert`` is a file. + Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed. + """ + cert = ctx.params.get("cert") + is_adhoc = cert == "adhoc" + + try: + import ssl + except ImportError: + is_context = False + else: + is_context = isinstance(cert, ssl.SSLContext) + + if value is not None: + if is_adhoc: + raise click.BadParameter( + 'When "--cert" is "adhoc", "--key" is not used.', ctx, param + ) + + if is_context: + raise click.BadParameter( + 'When "--cert" is an SSLContext object, "--key" is not used.', + ctx, + param, + ) + + if not cert: + raise click.BadParameter('"--cert" must also be specified.', ctx, param) + + ctx.params["cert"] = cert, value + + else: + if cert and not (is_adhoc or is_context): + raise click.BadParameter('Required when using "--cert".', ctx, param) + + return value + + +class SeparatedPathType(click.Path): + """Click option type that accepts a list of values separated by the + OS's path separator (``:``, ``;`` on Windows). Each value is + validated as a :class:`click.Path` type. + """ + + def convert( + self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None + ) -> t.Any: + items = self.split_envvar_value(value) + # can't call no-arg super() inside list comprehension until Python 3.12 + super_convert = super().convert + return [super_convert(item, param, ctx) for item in items] + + +@click.command("run", short_help="Run a development server.") +@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.") +@click.option("--port", "-p", default=5000, help="The port to bind to.") +@click.option( + "--cert", + type=CertParamType(), + help="Specify a certificate file to use HTTPS.", + is_eager=True, +) +@click.option( + "--key", + type=click.Path(exists=True, dir_okay=False, resolve_path=True), + callback=_validate_key, + expose_value=False, + help="The key file to use when specifying a certificate.", +) +@click.option( + "--reload/--no-reload", + default=None, + help="Enable or disable the reloader. By default the reloader " + "is active if debug is enabled.", +) +@click.option( + "--debugger/--no-debugger", + default=None, + help="Enable or disable the debugger. By default the debugger " + "is active if debug is enabled.", +) +@click.option( + "--with-threads/--without-threads", + default=True, + help="Enable or disable multithreading.", +) +@click.option( + "--extra-files", + default=None, + type=SeparatedPathType(), + help=( + "Extra files that trigger a reload on change. Multiple paths" + f" are separated by {os.path.pathsep!r}." + ), +) +@click.option( + "--exclude-patterns", + default=None, + type=SeparatedPathType(), + help=( + "Files matching these fnmatch patterns will not trigger a reload" + " on change. Multiple patterns are separated by" + f" {os.path.pathsep!r}." + ), +) +@pass_script_info +def run_command( + info: ScriptInfo, + host: str, + port: int, + reload: bool, + debugger: bool, + with_threads: bool, + cert: ssl.SSLContext | tuple[str, str | None] | t.Literal["adhoc"] | None, + extra_files: list[str] | None, + exclude_patterns: list[str] | None, +) -> None: + """Run a local development server. + + This server is for development purposes only. It does not provide + the stability, security, or performance of production WSGI servers. + + The reloader and debugger are enabled by default with the '--debug' + option. + """ + try: + app: WSGIApplication = info.load_app() + except Exception as e: + if is_running_from_reloader(): + # When reloading, print out the error immediately, but raise + # it later so the debugger or server can handle it. + traceback.print_exc() + err = e + + def app( + environ: WSGIEnvironment, start_response: StartResponse + ) -> cabc.Iterable[bytes]: + raise err from None + + else: + # When not reloading, raise the error immediately so the + # command fails. + raise e from None + + debug = get_debug_flag() + + if reload is None: + reload = debug + + if debugger is None: + debugger = debug + + show_server_banner(debug, info.app_import_path) + + run_simple( + host, + port, + app, + use_reloader=reload, + use_debugger=debugger, + threaded=with_threads, + ssl_context=cert, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + ) + + +run_command.params.insert(0, _debug_option) + + +@click.command("shell", short_help="Run a shell in the app context.") +@with_appcontext +def shell_command() -> None: + """Run an interactive Python shell in the context of a given + Flask application. The application will populate the default + namespace of this shell according to its configuration. + + This is useful for executing small snippets of management code + without having to manually configure the application. + """ + import code + + banner = ( + f"Python {sys.version} on {sys.platform}\n" + f"App: {current_app.import_name}\n" + f"Instance: {current_app.instance_path}" + ) + ctx: dict[str, t.Any] = {} + + # Support the regular Python interpreter startup script if someone + # is using it. + startup = os.environ.get("PYTHONSTARTUP") + if startup and os.path.isfile(startup): + with open(startup) as f: + eval(compile(f.read(), startup, "exec"), ctx) + + ctx.update(current_app.make_shell_context()) + + # Site, customize, or startup script can set a hook to call when + # entering interactive mode. The default one sets up readline with + # tab and history completion. + interactive_hook = getattr(sys, "__interactivehook__", None) + + if interactive_hook is not None: + try: + import readline + from rlcompleter import Completer + except ImportError: + pass + else: + # rlcompleter uses __main__.__dict__ by default, which is + # flask.__main__. Use the shell context instead. + readline.set_completer(Completer(ctx).complete) + + interactive_hook() + + code.interact(banner=banner, local=ctx) + + +@click.command("routes", short_help="Show the routes for the app.") +@click.option( + "--sort", + "-s", + type=click.Choice(("endpoint", "methods", "domain", "rule", "match")), + default="endpoint", + help=( + "Method to sort routes by. 'match' is the order that Flask will match routes" + " when dispatching a request." + ), +) +@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.") +@with_appcontext +def routes_command(sort: str, all_methods: bool) -> None: + """Show all registered routes with endpoints and methods.""" + rules = list(current_app.url_map.iter_rules()) + + if not rules: + click.echo("No routes were registered.") + return + + ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"} + host_matching = current_app.url_map.host_matching + has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules) + rows = [] + + for rule in rules: + row = [ + rule.endpoint, + ", ".join(sorted((rule.methods or set()) - ignored_methods)), + ] + + if has_domain: + row.append((rule.host if host_matching else rule.subdomain) or "") + + row.append(rule.rule) + rows.append(row) + + headers = ["Endpoint", "Methods"] + sorts = ["endpoint", "methods"] + + if has_domain: + headers.append("Host" if host_matching else "Subdomain") + sorts.append("domain") + + headers.append("Rule") + sorts.append("rule") + + try: + rows.sort(key=itemgetter(sorts.index(sort))) + except ValueError: + pass + + rows.insert(0, headers) + widths = [max(len(row[i]) for row in rows) for i in range(len(headers))] + rows.insert(1, ["-" * w for w in widths]) + template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths)) + + for row in rows: + click.echo(template.format(*row)) + + +cli = FlaskGroup( + name="flask", + help="""\ +A general utility script for Flask applications. + +An application to load must be given with the '--app' option, +'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file +in the current directory. +""", +) + + +def main() -> None: + cli.main() + + +if __name__ == "__main__": + main() + + +Filepath: githubCode\src\flask\config.py: + +from __future__ import annotations + +import errno +import json +import os +import types +import typing as t + +from werkzeug.utils import import_string + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .sansio.app import App + + +T = t.TypeVar("T") + + +class ConfigAttribute(t.Generic[T]): + """Makes an attribute forward to the config""" + + def __init__( + self, name: str, get_converter: t.Callable[[t.Any], T] | None = None + ) -> None: + self.__name__ = name + self.get_converter = get_converter + + @t.overload + def __get__(self, obj: None, owner: None) -> te.Self: + ... + + @t.overload + def __get__(self, obj: App, owner: type[App]) -> T: + ... + + def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self: + if obj is None: + return self + + rv = obj.config[self.__name__] + + if self.get_converter is not None: + rv = self.get_converter(rv) + + return rv # type: ignore[no-any-return] + + def __set__(self, obj: App, value: t.Any) -> None: + obj.config[self.__name__] = value + + +class Config(dict): # type: ignore[type-arg] + """Works exactly like a dict but provides ways to fill it from files + or special dictionaries. There are two common patterns to populate the + config. + + Either you can fill the config from a config file:: + + app.config.from_pyfile('yourconfig.cfg') + + Or alternatively you can define the configuration options in the + module that calls :meth:`from_object` or provide an import path to + a module that should be loaded. It is also possible to tell it to + use the same module and with that provide the configuration values + just before the call:: + + DEBUG = True + SECRET_KEY = 'development key' + app.config.from_object(__name__) + + In both cases (loading from any Python file or loading from modules), + only uppercase keys are added to the config. This makes it possible to use + lowercase values in the config file for temporary values that are not added + to the config or to define the config keys in the same file that implements + the application. + + Probably the most interesting way to load configurations is from an + environment variable pointing to a file:: + + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + + In this case before launching the application you have to set this + environment variable to the file you want to use. On Linux and OS X + use the export statement:: + + export YOURAPPLICATION_SETTINGS='/path/to/config/file' + + On windows use `set` instead. + + :param root_path: path to which files are read relative from. When the + config object is created by the application, this is + the application's :attr:`~flask.Flask.root_path`. + :param defaults: an optional dictionary of default values + """ + + def __init__( + self, + root_path: str | os.PathLike[str], + defaults: dict[str, t.Any] | None = None, + ) -> None: + super().__init__(defaults or {}) + self.root_path = root_path + + def from_envvar(self, variable_name: str, silent: bool = False) -> bool: + """Loads a configuration from an environment variable pointing to + a configuration file. This is basically just a shortcut with nicer + error messages for this line of code:: + + app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) + + :param variable_name: name of the environment variable + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + """ + rv = os.environ.get(variable_name) + if not rv: + if silent: + return False + raise RuntimeError( + f"The environment variable {variable_name!r} is not set" + " and as such configuration could not be loaded. Set" + " this variable and make it point to a configuration" + " file" + ) + return self.from_pyfile(rv, silent=silent) + + def from_prefixed_env( + self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads + ) -> bool: + """Load any environment variables that start with ``FLASK_``, + dropping the prefix from the env key for the config key. Values + are passed through a loading function to attempt to convert them + to more specific types than strings. + + Keys are loaded in :func:`sorted` order. + + The default loading function attempts to parse values as any + valid JSON type, including dicts and lists. + + Specific items in nested dicts can be set by separating the + keys with double underscores (``__``). If an intermediate key + doesn't exist, it will be initialized to an empty dict. + + :param prefix: Load env vars that start with this prefix, + separated with an underscore (``_``). + :param loads: Pass each string value to this function and use + the returned value as the config value. If any error is + raised it is ignored and the value remains a string. The + default is :func:`json.loads`. + + .. versionadded:: 2.1 + """ + prefix = f"{prefix}_" + len_prefix = len(prefix) + + for key in sorted(os.environ): + if not key.startswith(prefix): + continue + + value = os.environ[key] + + try: + value = loads(value) + except Exception: + # Keep the value as a string if loading failed. + pass + + # Change to key.removeprefix(prefix) on Python >= 3.9. + key = key[len_prefix:] + + if "__" not in key: + # A non-nested key, set directly. + self[key] = value + continue + + # Traverse nested dictionaries with keys separated by "__". + current = self + *parts, tail = key.split("__") + + for part in parts: + # If an intermediate dict does not exist, create it. + if part not in current: + current[part] = {} + + current = current[part] + + current[tail] = value + + return True + + def from_pyfile( + self, filename: str | os.PathLike[str], silent: bool = False + ) -> bool: + """Updates the values in the config from a Python file. This function + behaves as if the file was imported as module with the + :meth:`from_object` function. + + :param filename: the filename of the config. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + + .. versionadded:: 0.7 + `silent` parameter. + """ + filename = os.path.join(self.root_path, filename) + d = types.ModuleType("config") + d.__file__ = filename + try: + with open(filename, mode="rb") as config_file: + exec(compile(config_file.read(), filename, "exec"), d.__dict__) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): + return False + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + self.from_object(d) + return True + + def from_object(self, obj: object | str) -> None: + """Updates the values from the given object. An object can be of one + of the following two types: + + - a string: in this case the object with that name will be imported + - an actual object reference: that object is used directly + + Objects are usually either modules or classes. :meth:`from_object` + loads only the uppercase attributes of the module/class. A ``dict`` + object will not work with :meth:`from_object` because the keys of a + ``dict`` are not attributes of the ``dict`` class. + + Example of module-based configuration:: + + app.config.from_object('yourapplication.default_config') + from yourapplication import default_config + app.config.from_object(default_config) + + Nothing is done to the object before loading. If the object is a + class and has ``@property`` attributes, it needs to be + instantiated before being passed to this method. + + You should not use this function to load the actual configuration but + rather configuration defaults. The actual config should be loaded + with :meth:`from_pyfile` and ideally from a location not within the + package because the package might be installed system wide. + + See :ref:`config-dev-prod` for an example of class-based configuration + using :meth:`from_object`. + + :param obj: an import name or object + """ + if isinstance(obj, str): + obj = import_string(obj) + for key in dir(obj): + if key.isupper(): + self[key] = getattr(obj, key) + + def from_file( + self, + filename: str | os.PathLike[str], + load: t.Callable[[t.IO[t.Any]], t.Mapping[str, t.Any]], + silent: bool = False, + text: bool = True, + ) -> bool: + """Update the values in the config from a file that is loaded + using the ``load`` parameter. The loaded data is passed to the + :meth:`from_mapping` method. + + .. code-block:: python + + import json + app.config.from_file("config.json", load=json.load) + + import tomllib + app.config.from_file("config.toml", load=tomllib.load, text=False) + + :param filename: The path to the data file. This can be an + absolute path or relative to the config root path. + :param load: A callable that takes a file handle and returns a + mapping of loaded data from the file. + :type load: ``Callable[[Reader], Mapping]`` where ``Reader`` + implements a ``read`` method. + :param silent: Ignore the file if it doesn't exist. + :param text: Open the file in text or binary mode. + :return: ``True`` if the file was loaded successfully. + + .. versionchanged:: 2.3 + The ``text`` parameter was added. + + .. versionadded:: 2.0 + """ + filename = os.path.join(self.root_path, filename) + + try: + with open(filename, "r" if text else "rb") as f: + obj = load(f) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + + return self.from_mapping(obj) + + def from_mapping( + self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any + ) -> bool: + """Updates the config like :meth:`update` ignoring items with + non-upper keys. + + :return: Always returns ``True``. + + .. versionadded:: 0.11 + """ + mappings: dict[str, t.Any] = {} + if mapping is not None: + mappings.update(mapping) + mappings.update(kwargs) + for key, value in mappings.items(): + if key.isupper(): + self[key] = value + return True + + def get_namespace( + self, namespace: str, lowercase: bool = True, trim_namespace: bool = True + ) -> dict[str, t.Any]: + """Returns a dictionary containing a subset of configuration options + that match the specified namespace/prefix. Example usage:: + + app.config['IMAGE_STORE_TYPE'] = 'fs' + app.config['IMAGE_STORE_PATH'] = '/var/app/images' + app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' + image_store_config = app.config.get_namespace('IMAGE_STORE_') + + The resulting dictionary `image_store_config` would look like:: + + { + 'type': 'fs', + 'path': '/var/app/images', + 'base_url': 'http://img.website.com' + } + + This is often useful when configuration options map directly to + keyword arguments in functions or class constructors. + + :param namespace: a configuration namespace + :param lowercase: a flag indicating if the keys of the resulting + dictionary should be lowercase + :param trim_namespace: a flag indicating if the keys of the resulting + dictionary should not include the namespace + + .. versionadded:: 0.11 + """ + rv = {} + for k, v in self.items(): + if not k.startswith(namespace): + continue + if trim_namespace: + key = k[len(namespace) :] + else: + key = k + if lowercase: + key = key.lower() + rv[key] = v + return rv + + def __repr__(self) -> str: + return f"<{type(self).__name__} {dict.__repr__(self)}>" + + +Filepath: githubCode\src\flask\ctx.py: + +from __future__ import annotations + +import contextvars +import sys +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException + +from . import typing as ft +from .globals import _cv_app +from .globals import _cv_request +from .signals import appcontext_popped +from .signals import appcontext_pushed + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIEnvironment + + from .app import Flask + from .sessions import SessionMixin + from .wrappers import Request + + +# a singleton sentinel value for parameter defaults +_sentinel = object() + + +class _AppCtxGlobals: + """A plain object. Used as a namespace for storing data during an + application context. + + Creating an app context automatically creates this object, which is + made available as the :data:`g` proxy. + + .. describe:: 'key' in g + + Check whether an attribute is present. + + .. versionadded:: 0.10 + + .. describe:: iter(g) + + Return an iterator over the attribute names. + + .. versionadded:: 0.10 + """ + + # Define attr methods to let mypy know this is a namespace object + # that has arbitrary attributes. + + def __getattr__(self, name: str) -> t.Any: + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def __setattr__(self, name: str, value: t.Any) -> None: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + try: + del self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def get(self, name: str, default: t.Any | None = None) -> t.Any: + """Get an attribute by name, or a default value. Like + :meth:`dict.get`. + + :param name: Name of attribute to get. + :param default: Value to return if the attribute is not present. + + .. versionadded:: 0.10 + """ + return self.__dict__.get(name, default) + + def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: + """Get and remove an attribute by name. Like :meth:`dict.pop`. + + :param name: Name of attribute to pop. + :param default: Value to return if the attribute is not present, + instead of raising a ``KeyError``. + + .. versionadded:: 0.11 + """ + if default is _sentinel: + return self.__dict__.pop(name) + else: + return self.__dict__.pop(name, default) + + def setdefault(self, name: str, default: t.Any = None) -> t.Any: + """Get the value of an attribute if it is present, otherwise + set and return a default value. Like :meth:`dict.setdefault`. + + :param name: Name of attribute to get. + :param default: Value to set and return if the attribute is not + present. + + .. versionadded:: 0.11 + """ + return self.__dict__.setdefault(name, default) + + def __contains__(self, item: str) -> bool: + return item in self.__dict__ + + def __iter__(self) -> t.Iterator[str]: + return iter(self.__dict__) + + def __repr__(self) -> str: + ctx = _cv_app.get(None) + if ctx is not None: + return f"" + return object.__repr__(self) + + +def after_this_request( + f: ft.AfterRequestCallable[t.Any], +) -> ft.AfterRequestCallable[t.Any]: + """Executes a function after this request. This is useful to modify + response objects. The function is passed the response object and has + to return the same or a new one. + + Example:: + + @app.route('/') + def index(): + @after_this_request + def add_header(response): + response.headers['X-Foo'] = 'Parachute' + return response + return 'Hello World!' + + This is more useful if a function other than the view function wants to + modify a response. For instance think of a decorator that wants to add + some headers without converting the return value into a response object. + + .. versionadded:: 0.9 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'after_this_request' can only be used when a request" + " context is active, such as in a view function." + ) + + ctx._after_request_functions.append(f) + return f + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def copy_current_request_context(f: F) -> F: + """A helper function that decorates a function to retain the current + request context. This is useful when working with greenlets. The moment + the function is decorated a copy of the request context is created and + then pushed when the function is called. The current session is also + included in the copied request context. + + Example:: + + import gevent + from flask import copy_current_request_context + + @app.route('/') + def index(): + @copy_current_request_context + def do_some_work(): + # do some work here, it can access flask.request or + # flask.session like you would otherwise in the view function. + ... + gevent.spawn(do_some_work) + return 'Regular response' + + .. versionadded:: 0.10 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'copy_current_request_context' can only be used when a" + " request context is active, such as in a view function." + ) + + ctx = ctx.copy() + + def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any: + with ctx: # type: ignore[union-attr] + return ctx.app.ensure_sync(f)(*args, **kwargs) # type: ignore[union-attr] + + return update_wrapper(wrapper, f) # type: ignore[return-value] + + +def has_request_context() -> bool: + """If you have code that wants to test if a request context is there or + not this function can be used. For instance, you may want to take advantage + of request information if the request object is available, but fail + silently if it is unavailable. + + :: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and has_request_context(): + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + Alternatively you can also just test any of the context bound objects + (such as :class:`request` or :class:`g`) for truthness:: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and request: + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + .. versionadded:: 0.7 + """ + return _cv_request.get(None) is not None + + +def has_app_context() -> bool: + """Works like :func:`has_request_context` but for the application + context. You can also just do a boolean check on the + :data:`current_app` object instead. + + .. versionadded:: 0.9 + """ + return _cv_app.get(None) is not None + + +class AppContext: + """The app context contains application-specific information. An app + context is created and pushed at the beginning of each request if + one is not already active. An app context is also pushed when + running CLI commands. + """ + + def __init__(self, app: Flask) -> None: + self.app = app + self.url_adapter = app.create_url_adapter(None) + self.g: _AppCtxGlobals = app.app_ctx_globals_class() + self._cv_tokens: list[contextvars.Token[AppContext]] = [] + + def push(self) -> None: + """Binds the app context to the current context.""" + self._cv_tokens.append(_cv_app.set(self)) + appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the app context.""" + try: + if len(self._cv_tokens) == 1: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + ctx = _cv_app.get() + _cv_app.reset(self._cv_tokens.pop()) + + if ctx is not self: + raise AssertionError( + f"Popped wrong app context. ({ctx!r} instead of {self!r})" + ) + + appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) + + def __enter__(self) -> AppContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + +class RequestContext: + """The request context contains per-request information. The Flask + app creates and pushes it at the beginning of the request, then pops + it at the end of the request. It will create the URL adapter and + request object for the WSGI environment provided. + + Do not attempt to use this class directly, instead use + :meth:`~flask.Flask.test_request_context` and + :meth:`~flask.Flask.request_context` to create this object. + + When the request context is popped, it will evaluate all the + functions registered on the application for teardown execution + (:meth:`~flask.Flask.teardown_request`). + + The request context is automatically popped at the end of the + request. When using the interactive debugger, the context will be + restored so ``request`` is still accessible. Similarly, the test + client can preserve the context after the request ends. However, + teardown functions may already have closed some resources such as + database connections. + """ + + def __init__( + self, + app: Flask, + environ: WSGIEnvironment, + request: Request | None = None, + session: SessionMixin | None = None, + ) -> None: + self.app = app + if request is None: + request = app.request_class(environ) + request.json_module = app.json + self.request: Request = request + self.url_adapter = None + try: + self.url_adapter = app.create_url_adapter(self.request) + except HTTPException as e: + self.request.routing_exception = e + self.flashes: list[tuple[str, str]] | None = None + self.session: SessionMixin | None = session + # Functions that should be executed after the request on the response + # object. These will be called before the regular "after_request" + # functions. + self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = [] + + self._cv_tokens: list[ + tuple[contextvars.Token[RequestContext], AppContext | None] + ] = [] + + def copy(self) -> RequestContext: + """Creates a copy of this request context with the same request object. + This can be used to move a request context to a different greenlet. + Because the actual request object is the same this cannot be used to + move a request context to a different thread unless access to the + request object is locked. + + .. versionadded:: 0.10 + + .. versionchanged:: 1.1 + The current session object is used instead of reloading the original + data. This prevents `flask.session` pointing to an out-of-date object. + """ + return self.__class__( + self.app, + environ=self.request.environ, + request=self.request, + session=self.session, + ) + + def match_request(self) -> None: + """Can be overridden by a subclass to hook into the matching + of the request. + """ + try: + result = self.url_adapter.match(return_rule=True) # type: ignore + self.request.url_rule, self.request.view_args = result # type: ignore + except HTTPException as e: + self.request.routing_exception = e + + def push(self) -> None: + # Before we push the request context we have to ensure that there + # is an application context. + app_ctx = _cv_app.get(None) + + if app_ctx is None or app_ctx.app is not self.app: + app_ctx = self.app.app_context() + app_ctx.push() + else: + app_ctx = None + + self._cv_tokens.append((_cv_request.set(self), app_ctx)) + + # Open the session at the moment that the request context is available. + # This allows a custom open_session method to use the request context. + # Only open a new session if this is the first time the request was + # pushed, otherwise stream_with_context loses the session. + if self.session is None: + session_interface = self.app.session_interface + self.session = session_interface.open_session(self.app, self.request) + + if self.session is None: + self.session = session_interface.make_null_session(self.app) + + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the request context and unbinds it by doing that. This will + also trigger the execution of functions registered by the + :meth:`~flask.Flask.teardown_request` decorator. + + .. versionchanged:: 0.9 + Added the `exc` argument. + """ + clear_request = len(self._cv_tokens) == 1 + + try: + if clear_request: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + + request_close = getattr(self.request, "close", None) + if request_close is not None: + request_close() + finally: + ctx = _cv_request.get() + token, app_ctx = self._cv_tokens.pop() + _cv_request.reset(token) + + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + if clear_request: + ctx.request.environ["werkzeug.request"] = None + + if app_ctx is not None: + app_ctx.pop(exc) + + if ctx is not self: + raise AssertionError( + f"Popped wrong request context. ({ctx!r} instead of {self!r})" + ) + + def __enter__(self) -> RequestContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + def __repr__(self) -> str: + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" + ) + + +Filepath: githubCode\src\flask\debughelpers.py: + +from __future__ import annotations + +import typing as t + +from jinja2.loaders import BaseLoader +from werkzeug.routing import RequestRedirect + +from .blueprints import Blueprint +from .globals import request_ctx +from .sansio.app import App + +if t.TYPE_CHECKING: + from .sansio.scaffold import Scaffold + from .wrappers import Request + + +class UnexpectedUnicodeError(AssertionError, UnicodeError): + """Raised in places where we want some better error reporting for + unexpected unicode or binary data. + """ + + +class DebugFilesKeyError(KeyError, AssertionError): + """Raised from request.files during debugging. The idea is that it can + provide a better error message than just a generic KeyError/BadRequest. + """ + + def __init__(self, request: Request, key: str) -> None: + form_matches = request.form.getlist(key) + buf = [ + f"You tried to access the file {key!r} in the request.files" + " dictionary but it does not exist. The mimetype for the" + f" request is {request.mimetype!r} instead of" + " 'multipart/form-data' which means that no file contents" + " were transmitted. To fix this error you should provide" + ' enctype="multipart/form-data" in your form.' + ] + if form_matches: + names = ", ".join(repr(x) for x in form_matches) + buf.append( + "\n\nThe browser instead transmitted some file names. " + f"This was submitted: {names}" + ) + self.msg = "".join(buf) + + def __str__(self) -> str: + return self.msg + + +class FormDataRoutingRedirect(AssertionError): + """This exception is raised in debug mode if a routing redirect + would cause the browser to drop the method or body. This happens + when method is not GET, HEAD or OPTIONS and the status code is not + 307 or 308. + """ + + def __init__(self, request: Request) -> None: + exc = request.routing_exception + assert isinstance(exc, RequestRedirect) + buf = [ + f"A request was sent to '{request.url}', but routing issued" + f" a redirect to the canonical URL '{exc.new_url}'." + ] + + if f"{request.base_url}/" == exc.new_url.partition("?")[0]: + buf.append( + " The URL was defined with a trailing slash. Flask" + " will redirect to the URL with a trailing slash if it" + " was accessed without one." + ) + + buf.append( + " Send requests to the canonical URL, or use 307 or 308 for" + " routing redirects. Otherwise, browsers will drop form" + " data.\n\n" + "This exception is only raised in debug mode." + ) + super().__init__("".join(buf)) + + +def attach_enctype_error_multidict(request: Request) -> None: + """Patch ``request.files.__getitem__`` to raise a descriptive error + about ``enctype=multipart/form-data``. + + :param request: The request to patch. + :meta private: + """ + oldcls = request.files.__class__ + + class newcls(oldcls): # type: ignore[valid-type, misc] + def __getitem__(self, key: str) -> t.Any: + try: + return super().__getitem__(key) + except KeyError as e: + if key not in request.form: + raise + + raise DebugFilesKeyError(request, key).with_traceback( + e.__traceback__ + ) from None + + newcls.__name__ = oldcls.__name__ + newcls.__module__ = oldcls.__module__ + request.files.__class__ = newcls + + +def _dump_loader_info(loader: BaseLoader) -> t.Iterator[str]: + yield f"class: {type(loader).__module__}.{type(loader).__name__}" + for key, value in sorted(loader.__dict__.items()): + if key.startswith("_"): + continue + if isinstance(value, (tuple, list)): + if not all(isinstance(x, str) for x in value): + continue + yield f"{key}:" + for item in value: + yield f" - {item}" + continue + elif not isinstance(value, (str, int, float, bool)): + continue + yield f"{key}: {value!r}" + + +def explain_template_loading_attempts( + app: App, + template: str, + attempts: list[ + tuple[ + BaseLoader, + Scaffold, + tuple[str, str | None, t.Callable[[], bool] | None] | None, + ] + ], +) -> None: + """This should help developers understand what failed""" + info = [f"Locating template {template!r}:"] + total_found = 0 + blueprint = None + if request_ctx and request_ctx.request.blueprint is not None: + blueprint = request_ctx.request.blueprint + + for idx, (loader, srcobj, triple) in enumerate(attempts): + if isinstance(srcobj, App): + src_info = f"application {srcobj.import_name!r}" + elif isinstance(srcobj, Blueprint): + src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" + else: + src_info = repr(srcobj) + + info.append(f"{idx + 1:5}: trying loader of {src_info}") + + for line in _dump_loader_info(loader): + info.append(f" {line}") + + if triple is None: + detail = "no match" + else: + detail = f"found ({triple[1] or ''!r})" + total_found += 1 + info.append(f" -> {detail}") + + seems_fishy = False + if total_found == 0: + info.append("Error: the template could not be found.") + seems_fishy = True + elif total_found > 1: + info.append("Warning: multiple loaders returned a match for the template.") + seems_fishy = True + + if blueprint is not None and seems_fishy: + info.append( + " The template was looked up from an endpoint that belongs" + f" to the blueprint {blueprint!r}." + ) + info.append(" Maybe you did not place a template in the right folder?") + info.append(" See https://flask.palletsprojects.com/blueprints/#templates") + + app.logger.info("\n".join(info)) + + +Filepath: githubCode\src\flask\globals.py: + +from __future__ import annotations + +import typing as t +from contextvars import ContextVar + +from werkzeug.local import LocalProxy + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .ctx import _AppCtxGlobals + from .ctx import AppContext + from .ctx import RequestContext + from .sessions import SessionMixin + from .wrappers import Request + + +_no_app_msg = """\ +Working outside of application context. + +This typically means that you attempted to use functionality that needed +the current application. To solve this, set up an application context +with app.app_context(). See the documentation for more information.\ +""" +_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") +app_ctx: AppContext = LocalProxy( # type: ignore[assignment] + _cv_app, unbound_message=_no_app_msg +) +current_app: Flask = LocalProxy( # type: ignore[assignment] + _cv_app, "app", unbound_message=_no_app_msg +) +g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] + _cv_app, "g", unbound_message=_no_app_msg +) + +_no_req_msg = """\ +Working outside of request context. + +This typically means that you attempted to use functionality that needed +an active HTTP request. Consult the documentation on testing for +information about how to avoid this problem.\ +""" +_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx") +request_ctx: RequestContext = LocalProxy( # type: ignore[assignment] + _cv_request, unbound_message=_no_req_msg +) +request: Request = LocalProxy( # type: ignore[assignment] + _cv_request, "request", unbound_message=_no_req_msg +) +session: SessionMixin = LocalProxy( # type: ignore[assignment] + _cv_request, "session", unbound_message=_no_req_msg +) + + +Filepath: githubCode\src\flask\helpers.py: + +from __future__ import annotations + +import importlib.util +import os +import sys +import typing as t +from datetime import datetime +from functools import lru_cache +from functools import update_wrapper + +import werkzeug.utils +from werkzeug.exceptions import abort as _wz_abort +from werkzeug.utils import redirect as _wz_redirect +from werkzeug.wrappers import Response as BaseResponse + +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .globals import request_ctx +from .globals import session +from .signals import message_flashed + +if t.TYPE_CHECKING: # pragma: no cover + from .wrappers import Response + + +def get_debug_flag() -> bool: + """Get whether debug mode should be enabled for the app, indicated by the + :envvar:`FLASK_DEBUG` environment variable. The default is ``False``. + """ + val = os.environ.get("FLASK_DEBUG") + return bool(val and val.lower() not in {"0", "false", "no"}) + + +def get_load_dotenv(default: bool = True) -> bool: + """Get whether the user has disabled loading default dotenv files by + setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load + the files. + + :param default: What to return if the env var isn't set. + """ + val = os.environ.get("FLASK_SKIP_DOTENV") + + if not val: + return default + + return val.lower() in ("0", "false", "no") + + +def stream_with_context( + generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]], +) -> t.Iterator[t.AnyStr]: + """Request contexts disappear when the response is started on the server. + This is done for efficiency reasons and to make it less likely to encounter + memory leaks with badly written WSGI middlewares. The downside is that if + you are using streamed responses, the generator cannot access request bound + information any more. + + This function however can help you keep the context around for longer:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + @stream_with_context + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(generate()) + + Alternatively it can also be used around a specific generator:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(stream_with_context(generate())) + + .. versionadded:: 0.9 + """ + try: + gen = iter(generator_or_function) # type: ignore[arg-type] + except TypeError: + + def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any: + gen = generator_or_function(*args, **kwargs) # type: ignore[operator] + return stream_with_context(gen) + + return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type] + + def generator() -> t.Iterator[t.AnyStr | None]: + ctx = _cv_request.get(None) + if ctx is None: + raise RuntimeError( + "'stream_with_context' can only be used when a request" + " context is active, such as in a view function." + ) + with ctx: + # Dummy sentinel. Has to be inside the context block or we're + # not actually keeping the context around. + yield None + + # The try/finally is here so that if someone passes a WSGI level + # iterator in we're still running the cleanup logic. Generators + # don't need that because they are closed on their destruction + # automatically. + try: + yield from gen + finally: + if hasattr(gen, "close"): + gen.close() + + # The trick is to start the generator. Then the code execution runs until + # the first dummy None is yielded at which point the context was already + # pushed. This item is discarded. Then when the iteration continues the + # real generator is executed. + wrapped_g = generator() + next(wrapped_g) + return wrapped_g # type: ignore[return-value] + + +def make_response(*args: t.Any) -> Response: + """Sometimes it is necessary to set additional headers in a view. Because + views do not have to return response objects but can return a value that + is converted into a response object by Flask itself, it becomes tricky to + add headers to it. This function can be called instead of using a return + and you will get a response object which you can use to attach headers. + + If view looked like this and you want to add a new header:: + + def index(): + return render_template('index.html', foo=42) + + You can now do something like this:: + + def index(): + response = make_response(render_template('index.html', foo=42)) + response.headers['X-Parachutes'] = 'parachutes are cool' + return response + + This function accepts the very same arguments you can return from a + view function. This for example creates a response with a 404 error + code:: + + response = make_response(render_template('not_found.html'), 404) + + The other use case of this function is to force the return value of a + view function into a response which is helpful with view + decorators:: + + response = make_response(view_function()) + response.headers['X-Parachutes'] = 'parachutes are cool' + + Internally this function does the following things: + + - if no arguments are passed, it creates a new response argument + - if one argument is passed, :meth:`flask.Flask.make_response` + is invoked with it. + - if more than one argument is passed, the arguments are passed + to the :meth:`flask.Flask.make_response` function as tuple. + + .. versionadded:: 0.6 + """ + if not args: + return current_app.response_class() + if len(args) == 1: + args = args[0] + return current_app.make_response(args) + + +def url_for( + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, +) -> str: + """Generate a URL to the given endpoint with the given values. + + This requires an active request or application context, and calls + :meth:`current_app.url_for() `. See that method + for full documentation. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it is + external. + :param _external: If given, prefer the URL to be internal (False) or + require it to be external (True). External URLs include the + scheme and domain. When not in an active request, URLs are + external by default. + :param values: Values to use for the variable parts of the URL rule. + Unknown keys are appended as query string arguments, like + ``?a=b&c=d``. + + .. versionchanged:: 2.2 + Calls ``current_app.url_for``, allowing an app to override the + behavior. + + .. versionchanged:: 0.10 + The ``_scheme`` parameter was added. + + .. versionchanged:: 0.9 + The ``_anchor`` and ``_method`` parameters were added. + + .. versionchanged:: 0.9 + Calls ``app.handle_url_build_error`` on build errors. + """ + return current_app.url_for( + endpoint, + _anchor=_anchor, + _method=_method, + _scheme=_scheme, + _external=_external, + **values, + ) + + +def redirect( + location: str, code: int = 302, Response: type[BaseResponse] | None = None +) -> BaseResponse: + """Create a redirect response object. + + If :data:`~flask.current_app` is available, it will use its + :meth:`~flask.Flask.redirect` method, otherwise it will use + :func:`werkzeug.utils.redirect`. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + :param Response: The response class to use. Not used when + ``current_app`` is active, which uses ``app.response_class``. + + .. versionadded:: 2.2 + Calls ``current_app.redirect`` if available instead of always + using Werkzeug's default ``redirect``. + """ + if current_app: + return current_app.redirect(location, code=code) + + return _wz_redirect(location, code=code, Response=Response) + + +def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given + status code. + + If :data:`~flask.current_app` is available, it will call its + :attr:`~flask.Flask.aborter` object, otherwise it will use + :func:`werkzeug.exceptions.abort`. + + :param code: The status code for the exception, which must be + registered in ``app.aborter``. + :param args: Passed to the exception. + :param kwargs: Passed to the exception. + + .. versionadded:: 2.2 + Calls ``current_app.aborter`` if available instead of always + using Werkzeug's default ``abort``. + """ + if current_app: + current_app.aborter(code, *args, **kwargs) + + _wz_abort(code, *args, **kwargs) + + +def get_template_attribute(template_name: str, attribute: str) -> t.Any: + """Loads a macro (or variable) a template exports. This can be used to + invoke a macro from within Python code. If you for example have a + template named :file:`_cider.html` with the following contents: + + .. sourcecode:: html+jinja + + {% macro hello(name) %}Hello {{ name }}!{% endmacro %} + + You can access this from Python code like this:: + + hello = get_template_attribute('_cider.html', 'hello') + return hello('World') + + .. versionadded:: 0.2 + + :param template_name: the name of the template + :param attribute: the name of the variable of macro to access + """ + return getattr(current_app.jinja_env.get_template(template_name).module, attribute) + + +def flash(message: str, category: str = "message") -> None: + """Flashes a message to the next request. In order to remove the + flashed message from the session and to display it to the user, + the template has to call :func:`get_flashed_messages`. + + .. versionchanged:: 0.3 + `category` parameter added. + + :param message: the message to be flashed. + :param category: the category for the message. The following values + are recommended: ``'message'`` for any kind of message, + ``'error'`` for errors, ``'info'`` for information + messages and ``'warning'`` for warnings. However any + kind of string can be used as category. + """ + # Original implementation: + # + # session.setdefault('_flashes', []).append((category, message)) + # + # This assumed that changes made to mutable structures in the session are + # always in sync with the session object, which is not true for session + # implementations that use external storage for keeping their keys/values. + flashes = session.get("_flashes", []) + flashes.append((category, message)) + session["_flashes"] = flashes + app = current_app._get_current_object() # type: ignore + message_flashed.send( + app, + _async_wrapper=app.ensure_sync, + message=message, + category=category, + ) + + +def get_flashed_messages( + with_categories: bool = False, category_filter: t.Iterable[str] = () +) -> list[str] | list[tuple[str, str]]: + """Pulls all flashed messages from the session and returns them. + Further calls in the same request to the function will return + the same messages. By default just the messages are returned, + but when `with_categories` is set to ``True``, the return value will + be a list of tuples in the form ``(category, message)`` instead. + + Filter the flashed messages to one or more categories by providing those + categories in `category_filter`. This allows rendering categories in + separate html blocks. The `with_categories` and `category_filter` + arguments are distinct: + + * `with_categories` controls whether categories are returned with message + text (``True`` gives a tuple, where ``False`` gives just the message text). + * `category_filter` filters the messages down to only those matching the + provided categories. + + See :doc:`/patterns/flashing` for examples. + + .. versionchanged:: 0.3 + `with_categories` parameter added. + + .. versionchanged:: 0.9 + `category_filter` parameter added. + + :param with_categories: set to ``True`` to also receive categories. + :param category_filter: filter of categories to limit return values. Only + categories in the list will be returned. + """ + flashes = request_ctx.flashes + if flashes is None: + flashes = session.pop("_flashes") if "_flashes" in session else [] + request_ctx.flashes = flashes + if category_filter: + flashes = list(filter(lambda f: f[0] in category_filter, flashes)) + if not with_categories: + return [x[1] for x in flashes] + return flashes + + +def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]: + if kwargs.get("max_age") is None: + kwargs["max_age"] = current_app.get_send_file_max_age + + kwargs.update( + environ=request.environ, + use_x_sendfile=current_app.config["USE_X_SENDFILE"], + response_class=current_app.response_class, + _root_path=current_app.root_path, # type: ignore + ) + return kwargs + + +def send_file( + path_or_file: os.PathLike[t.AnyStr] | str | t.BinaryIO, + mimetype: str | None = None, + as_attachment: bool = False, + download_name: str | None = None, + conditional: bool = True, + etag: bool | str = True, + last_modified: datetime | int | float | None = None, + max_age: None | (int | t.Callable[[str | None], int | None]) = None, +) -> Response: + """Send the contents of a file to the client. + + The first argument can be a file path or a file-like object. Paths + are preferred in most cases because Werkzeug can manage the file and + get extra information from the path. Passing a file-like object + requires that the file is opened in binary mode, and is mostly + useful when building a file in memory with :class:`io.BytesIO`. + + Never pass file paths provided by a user. The path is assumed to be + trusted, so a user could craft a path to access a file you didn't + intend. Use :func:`send_from_directory` to safely serve + user-requested paths from within a directory. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, configuring Flask with + ``USE_X_SENDFILE = True`` will tell the server to send the given + path, which is much more efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + + .. versionchanged:: 2.0 + ``download_name`` replaces the ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces the ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces the ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + Passing a file-like object that inherits from + :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather + than sending an empty file. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionchanged:: 1.1 + ``filename`` may be a :class:`~os.PathLike` object. + + .. versionchanged:: 1.1 + Passing a :class:`~io.BytesIO` object supports range requests. + + .. versionchanged:: 1.0.3 + Filenames are encoded with ASCII instead of Latin-1 for broader + compatibility with WSGI servers. + + .. versionchanged:: 1.0 + UTF-8 filenames as specified in :rfc:`2231` are supported. + + .. versionchanged:: 0.12 + The filename is no longer automatically inferred from file + objects. If you want to use automatic MIME and etag support, + pass a filename via ``filename_or_fp`` or + ``attachment_filename``. + + .. versionchanged:: 0.12 + ``attachment_filename`` is preferred over ``filename`` for MIME + detection. + + .. versionchanged:: 0.9 + ``cache_timeout`` defaults to + :meth:`Flask.get_send_file_max_age`. + + .. versionchanged:: 0.7 + MIME guessing and etag support for file-like objects was + removed because it was unreliable. Pass a filename if you are + able to, otherwise attach an etag yourself. + + .. versionchanged:: 0.5 + The ``add_etags``, ``cache_timeout`` and ``conditional`` + parameters were added. The default behavior is to add etags. + + .. versionadded:: 0.2 + """ + return werkzeug.utils.send_file( # type: ignore[return-value] + **_prepare_send_file_kwargs( + path_or_file=path_or_file, + environ=request.environ, + mimetype=mimetype, + as_attachment=as_attachment, + download_name=download_name, + conditional=conditional, + etag=etag, + last_modified=last_modified, + max_age=max_age, + ) + ) + + +def send_from_directory( + directory: os.PathLike[str] | str, + path: os.PathLike[str] | str, + **kwargs: t.Any, +) -> Response: + """Send a file from within a directory using :func:`send_file`. + + .. code-block:: python + + @app.route("/uploads/") + def download_file(name): + return send_from_directory( + app.config['UPLOAD_FOLDER'], name, as_attachment=True + ) + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under, + relative to the current application's root path. + :param path: The path to the file to send, relative to + ``directory``. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionchanged:: 2.0 + ``path`` replaces the ``filename`` parameter. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionadded:: 0.5 + """ + return werkzeug.utils.send_from_directory( # type: ignore[return-value] + directory, path, **_prepare_send_file_kwargs(**kwargs) + ) + + +def get_root_path(import_name: str) -> str: + """Find the root path of a package, or the path that contains a + module. If it cannot be found, returns the current working + directory. + + Not to be confused with the value returned by :func:`find_package`. + + :meta private: + """ + # Module already imported and has a file attribute. Use that first. + mod = sys.modules.get(import_name) + + if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None: + return os.path.dirname(os.path.abspath(mod.__file__)) + + # Next attempt: check the loader. + try: + spec = importlib.util.find_spec(import_name) + + if spec is None: + raise ValueError + except (ImportError, ValueError): + loader = None + else: + loader = spec.loader + + # Loader does not exist or we're referring to an unloaded main + # module or a main module without path (interactive sessions), go + # with the current working directory. + if loader is None: + return os.getcwd() + + if hasattr(loader, "get_filename"): + filepath = loader.get_filename(import_name) + else: + # Fall back to imports. + __import__(import_name) + mod = sys.modules[import_name] + filepath = getattr(mod, "__file__", None) + + # If we don't have a file path it might be because it is a + # namespace package. In this case pick the root path from the + # first module that is contained in the package. + if filepath is None: + raise RuntimeError( + "No root path can be found for the provided module" + f" {import_name!r}. This can happen because the module" + " came from an import hook that does not provide file" + " name information or because it's a namespace package." + " In this case the root path needs to be explicitly" + " provided." + ) + + # filepath is import_name.py for a module, or __init__.py for a package. + return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return] + + +@lru_cache(maxsize=None) +def _split_blueprint_path(name: str) -> list[str]: + out: list[str] = [name] + + if "." in name: + out.extend(_split_blueprint_path(name.rpartition(".")[0])) + + return out + + +Filepath: githubCode\src\flask\logging.py: + +from __future__ import annotations + +import logging +import sys +import typing as t + +from werkzeug.local import LocalProxy + +from .globals import request + +if t.TYPE_CHECKING: # pragma: no cover + from .sansio.app import App + + +@LocalProxy +def wsgi_errors_stream() -> t.TextIO: + """Find the most appropriate error stream for the application. If a request + is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``. + + If you configure your own :class:`logging.StreamHandler`, you may want to + use this for the stream. If you are using file or dict configuration and + can't import this directly, you can refer to it as + ``ext://flask.logging.wsgi_errors_stream``. + """ + if request: + return request.environ["wsgi.errors"] # type: ignore[no-any-return] + + return sys.stderr + + +def has_level_handler(logger: logging.Logger) -> bool: + """Check if there is a handler in the logging chain that will handle the + given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`. + """ + level = logger.getEffectiveLevel() + current = logger + + while current: + if any(handler.level <= level for handler in current.handlers): + return True + + if not current.propagate: + break + + current = current.parent # type: ignore + + return False + + +#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format +#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``. +default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore +default_handler.setFormatter( + logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s") +) + + +def create_logger(app: App) -> logging.Logger: + """Get the Flask app's logger and configure it if needed. + + The logger name will be the same as + :attr:`app.import_name `. + + When :attr:`~flask.Flask.debug` is enabled, set the logger level to + :data:`logging.DEBUG` if it is not set. + + If there is no handler for the logger's effective level, add a + :class:`~logging.StreamHandler` for + :func:`~flask.logging.wsgi_errors_stream` with a basic format. + """ + logger = logging.getLogger(app.name) + + if app.debug and not logger.level: + logger.setLevel(logging.DEBUG) + + if not has_level_handler(logger): + logger.addHandler(default_handler) + + return logger + + +Filepath: githubCode\src\flask\sessions.py: + +from __future__ import annotations + +import hashlib +import typing as t +from collections.abc import MutableMapping +from datetime import datetime +from datetime import timezone + +from itsdangerous import BadSignature +from itsdangerous import URLSafeTimedSerializer +from werkzeug.datastructures import CallbackDict + +from .json.tag import TaggedJSONSerializer + +if t.TYPE_CHECKING: # pragma: no cover + import typing_extensions as te + + from .app import Flask + from .wrappers import Request + from .wrappers import Response + + +# TODO generic when Python > 3.8 +class SessionMixin(MutableMapping): # type: ignore[type-arg] + """Expands a basic dictionary with session attributes.""" + + @property + def permanent(self) -> bool: + """This reflects the ``'_permanent'`` key in the dict.""" + return self.get("_permanent", False) + + @permanent.setter + def permanent(self, value: bool) -> None: + self["_permanent"] = bool(value) + + #: Some implementations can detect whether a session is newly + #: created, but that is not guaranteed. Use with caution. The mixin + # default is hard-coded ``False``. + new = False + + #: Some implementations can detect changes to the session and set + #: this when that happens. The mixin default is hard coded to + #: ``True``. + modified = True + + #: Some implementations can detect when session data is read or + #: written and set this when that happens. The mixin default is hard + #: coded to ``True``. + accessed = True + + +# TODO generic when Python > 3.8 +class SecureCookieSession(CallbackDict, SessionMixin): # type: ignore[type-arg] + """Base class for sessions based on signed cookies. + + This session backend will set the :attr:`modified` and + :attr:`accessed` attributes. It cannot reliably track whether a + session is new (vs. empty), so :attr:`new` remains hard coded to + ``False``. + """ + + #: When data is changed, this is set to ``True``. Only the session + #: dictionary itself is tracked; if the session contains mutable + #: data (for example a nested dict) then this must be set to + #: ``True`` manually when modifying that data. The session cookie + #: will only be written to the response if this is ``True``. + modified = False + + #: When data is read or written, this is set to ``True``. Used by + # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie`` + #: header, which allows caching proxies to cache different pages for + #: different users. + accessed = False + + def __init__(self, initial: t.Any = None) -> None: + def on_update(self: te.Self) -> None: + self.modified = True + self.accessed = True + + super().__init__(initial, on_update) + + def __getitem__(self, key: str) -> t.Any: + self.accessed = True + return super().__getitem__(key) + + def get(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().get(key, default) + + def setdefault(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().setdefault(key, default) + + +class NullSession(SecureCookieSession): + """Class used to generate nicer error messages if sessions are not + available. Will still allow read-only access to the empty session + but fail on setting. + """ + + def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + raise RuntimeError( + "The session is unavailable because no secret " + "key was set. Set the secret_key on the " + "application to something unique and secret." + ) + + __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950 + del _fail + + +class SessionInterface: + """The basic interface you have to implement in order to replace the + default session interface which uses werkzeug's securecookie + implementation. The only methods you have to implement are + :meth:`open_session` and :meth:`save_session`, the others have + useful defaults which you don't need to change. + + The session object returned by the :meth:`open_session` method has to + provide a dictionary like interface plus the properties and methods + from the :class:`SessionMixin`. We recommend just subclassing a dict + and adding that mixin:: + + class Session(dict, SessionMixin): + pass + + If :meth:`open_session` returns ``None`` Flask will call into + :meth:`make_null_session` to create a session that acts as replacement + if the session support cannot work because some requirement is not + fulfilled. The default :class:`NullSession` class that is created + will complain that the secret key was not set. + + To replace the session interface on an application all you have to do + is to assign :attr:`flask.Flask.session_interface`:: + + app = Flask(__name__) + app.session_interface = MySessionInterface() + + Multiple requests with the same session may be sent and handled + concurrently. When implementing a new session interface, consider + whether reads or writes to the backing store must be synchronized. + There is no guarantee on the order in which the session for each + request is opened or saved, it will occur in the order that requests + begin and end processing. + + .. versionadded:: 0.8 + """ + + #: :meth:`make_null_session` will look here for the class that should + #: be created when a null session is requested. Likewise the + #: :meth:`is_null_session` method will perform a typecheck against + #: this type. + null_session_class = NullSession + + #: A flag that indicates if the session interface is pickle based. + #: This can be used by Flask extensions to make a decision in regards + #: to how to deal with the session object. + #: + #: .. versionadded:: 0.10 + pickle_based = False + + def make_null_session(self, app: Flask) -> NullSession: + """Creates a null session which acts as a replacement object if the + real session support could not be loaded due to a configuration + error. This mainly aids the user experience because the job of the + null session is to still support lookup without complaining but + modifications are answered with a helpful error message of what + failed. + + This creates an instance of :attr:`null_session_class` by default. + """ + return self.null_session_class() + + def is_null_session(self, obj: object) -> bool: + """Checks if a given object is a null session. Null sessions are + not asked to be saved. + + This checks if the object is an instance of :attr:`null_session_class` + by default. + """ + return isinstance(obj, self.null_session_class) + + def get_cookie_name(self, app: Flask) -> str: + """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``.""" + return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return] + + def get_cookie_domain(self, app: Flask) -> str | None: + """The value of the ``Domain`` parameter on the session cookie. If not set, + browsers will only send the cookie to the exact domain it was set from. + Otherwise, they will send it to any subdomain of the given value as well. + + Uses the :data:`SESSION_COOKIE_DOMAIN` config. + + .. versionchanged:: 2.3 + Not set by default, does not fall back to ``SERVER_NAME``. + """ + return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return] + + def get_cookie_path(self, app: Flask) -> str: + """Returns the path for which the cookie should be valid. The + default implementation uses the value from the ``SESSION_COOKIE_PATH`` + config var if it's set, and falls back to ``APPLICATION_ROOT`` or + uses ``/`` if it's ``None``. + """ + return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] # type: ignore[no-any-return] + + def get_cookie_httponly(self, app: Flask) -> bool: + """Returns True if the session cookie should be httponly. This + currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` + config var. + """ + return app.config["SESSION_COOKIE_HTTPONLY"] # type: ignore[no-any-return] + + def get_cookie_secure(self, app: Flask) -> bool: + """Returns True if the cookie should be secure. This currently + just returns the value of the ``SESSION_COOKIE_SECURE`` setting. + """ + return app.config["SESSION_COOKIE_SECURE"] # type: ignore[no-any-return] + + def get_cookie_samesite(self, app: Flask) -> str | None: + """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the + ``SameSite`` attribute. This currently just returns the value of + the :data:`SESSION_COOKIE_SAMESITE` setting. + """ + return app.config["SESSION_COOKIE_SAMESITE"] # type: ignore[no-any-return] + + def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None: + """A helper method that returns an expiration date for the session + or ``None`` if the session is linked to the browser session. The + default implementation returns now + the permanent session + lifetime configured on the application. + """ + if session.permanent: + return datetime.now(timezone.utc) + app.permanent_session_lifetime + return None + + def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool: + """Used by session backends to determine if a ``Set-Cookie`` header + should be set for this session cookie for this response. If the session + has been modified, the cookie is set. If the session is permanent and + the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is + always set. + + This check is usually skipped if the session was deleted. + + .. versionadded:: 0.11 + """ + + return session.modified or ( + session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"] + ) + + def open_session(self, app: Flask, request: Request) -> SessionMixin | None: + """This is called at the beginning of each request, after + pushing the request context, before matching the URL. + + This must return an object which implements a dictionary-like + interface as well as the :class:`SessionMixin` interface. + + This will return ``None`` to indicate that loading failed in + some way that is not immediately an error. The request + context will fall back to using :meth:`make_null_session` + in this case. + """ + raise NotImplementedError() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + """This is called at the end of each request, after generating + a response, before removing the request context. It is skipped + if :meth:`is_null_session` returns ``True``. + """ + raise NotImplementedError() + + +session_json_serializer = TaggedJSONSerializer() + + +class SecureCookieSessionInterface(SessionInterface): + """The default session interface that stores sessions in signed cookies + through the :mod:`itsdangerous` module. + """ + + #: the salt that should be applied on top of the secret key for the + #: signing of cookie based sessions. + salt = "cookie-session" + #: the hash function to use for the signature. The default is sha1 + digest_method = staticmethod(hashlib.sha1) + #: the name of the itsdangerous supported key derivation. The default + #: is hmac. + key_derivation = "hmac" + #: A python serializer for the payload. The default is a compact + #: JSON derived serializer with support for some extra Python types + #: such as datetime objects or tuples. + serializer = session_json_serializer + session_class = SecureCookieSession + + def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None: + if not app.secret_key: + return None + signer_kwargs = dict( + key_derivation=self.key_derivation, digest_method=self.digest_method + ) + return URLSafeTimedSerializer( + app.secret_key, + salt=self.salt, + serializer=self.serializer, + signer_kwargs=signer_kwargs, + ) + + def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None: + s = self.get_signing_serializer(app) + if s is None: + return None + val = request.cookies.get(self.get_cookie_name(app)) + if not val: + return self.session_class() + max_age = int(app.permanent_session_lifetime.total_seconds()) + try: + data = s.loads(val, max_age=max_age) + return self.session_class(data) + except BadSignature: + return self.session_class() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + name = self.get_cookie_name(app) + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + secure = self.get_cookie_secure(app) + samesite = self.get_cookie_samesite(app) + httponly = self.get_cookie_httponly(app) + + # Add a "Vary: Cookie" header if the session was accessed at all. + if session.accessed: + response.vary.add("Cookie") + + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: + response.delete_cookie( + name, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + httponly=httponly, + ) + response.vary.add("Cookie") + + return + + if not self.should_set_cookie(app, session): + return + + expires = self.get_expiration_time(app, session) + val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore + response.set_cookie( + name, + val, # type: ignore + expires=expires, + httponly=httponly, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + ) + response.vary.add("Cookie") + + +Filepath: githubCode\src\flask\signals.py: + +from __future__ import annotations + +from blinker import Namespace + +# This namespace is only for signals provided by Flask itself. +_signals = Namespace() + +template_rendered = _signals.signal("template-rendered") +before_render_template = _signals.signal("before-render-template") +request_started = _signals.signal("request-started") +request_finished = _signals.signal("request-finished") +request_tearing_down = _signals.signal("request-tearing-down") +got_request_exception = _signals.signal("got-request-exception") +appcontext_tearing_down = _signals.signal("appcontext-tearing-down") +appcontext_pushed = _signals.signal("appcontext-pushed") +appcontext_popped = _signals.signal("appcontext-popped") +message_flashed = _signals.signal("message-flashed") + + +Filepath: githubCode\src\flask\templating.py: + +from __future__ import annotations + +import typing as t + +from jinja2 import BaseLoader +from jinja2 import Environment as BaseEnvironment +from jinja2 import Template +from jinja2 import TemplateNotFound + +from .globals import _cv_app +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .helpers import stream_with_context +from .signals import before_render_template +from .signals import template_rendered + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .sansio.app import App + from .sansio.scaffold import Scaffold + + +def _default_template_ctx_processor() -> dict[str, t.Any]: + """Default template context processor. Injects `request`, + `session` and `g`. + """ + appctx = _cv_app.get(None) + reqctx = _cv_request.get(None) + rv: dict[str, t.Any] = {} + if appctx is not None: + rv["g"] = appctx.g + if reqctx is not None: + rv["request"] = reqctx.request + rv["session"] = reqctx.session + return rv + + +class Environment(BaseEnvironment): + """Works like a regular Jinja2 environment but has some additional + knowledge of how Flask's blueprint works so that it can prepend the + name of the blueprint to referenced templates if necessary. + """ + + def __init__(self, app: App, **options: t.Any) -> None: + if "loader" not in options: + options["loader"] = app.create_global_jinja_loader() + BaseEnvironment.__init__(self, **options) + self.app = app + + +class DispatchingJinjaLoader(BaseLoader): + """A loader that looks for templates in the application and all + the blueprint folders. + """ + + def __init__(self, app: App) -> None: + self.app = app + + def get_source( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: + return self._get_source_explained(environment, template) + return self._get_source_fast(environment, template) + + def _get_source_explained( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + attempts = [] + rv: tuple[str, str | None, t.Callable[[], bool] | None] | None + trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None + + for srcobj, loader in self._iter_loaders(template): + try: + rv = loader.get_source(environment, template) + if trv is None: + trv = rv + except TemplateNotFound: + rv = None + attempts.append((loader, srcobj, rv)) + + from .debughelpers import explain_template_loading_attempts + + explain_template_loading_attempts(self.app, template, attempts) + + if trv is not None: + return trv + raise TemplateNotFound(template) + + def _get_source_fast( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + for _srcobj, loader in self._iter_loaders(template): + try: + return loader.get_source(environment, template) + except TemplateNotFound: + continue + raise TemplateNotFound(template) + + def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]: + loader = self.app.jinja_loader + if loader is not None: + yield self.app, loader + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + yield blueprint, loader + + def list_templates(self) -> list[str]: + result = set() + loader = self.app.jinja_loader + if loader is not None: + result.update(loader.list_templates()) + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + for template in loader.list_templates(): + result.add(template) + + return list(result) + + +def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + rv = template.render(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + return rv + + +def render_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> str: + """Render a template by name with the given context. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _render(app, template, context) + + +def render_template_string(source: str, **context: t.Any) -> str: + """Render a template from the given source string with the given + context. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _render(app, template, context) + + +def _stream( + app: Flask, template: Template, context: dict[str, t.Any] +) -> t.Iterator[str]: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + def generate() -> t.Iterator[str]: + yield from template.generate(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + rv = generate() + + # If a request context is active, keep it while generating. + if request: + rv = stream_with_context(rv) + + return rv + + +def stream_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> t.Iterator[str]: + """Render a template by name with the given context as a stream. + This returns an iterator of strings, which can be used as a + streaming response from a view. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _stream(app, template, context) + + +def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]: + """Render a template from the given source string with the given + context as a stream. This returns an iterator of strings, which can + be used as a streaming response from a view. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _stream(app, template, context) + + +Filepath: githubCode\src\flask\testing.py: + +from __future__ import annotations + +import importlib.metadata +import typing as t +from contextlib import contextmanager +from contextlib import ExitStack +from copy import copy +from types import TracebackType +from urllib.parse import urlsplit + +import werkzeug.test +from click.testing import CliRunner +from werkzeug.test import Client +from werkzeug.wrappers import Request as BaseRequest + +from .cli import ScriptInfo +from .sessions import SessionMixin + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIEnvironment + from werkzeug.test import TestResponse + + from .app import Flask + + +class EnvironBuilder(werkzeug.test.EnvironBuilder): + """An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the + application. + + :param app: The Flask application to configure the environment from. + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + + def __init__( + self, + app: Flask, + path: str = "/", + base_url: str | None = None, + subdomain: str | None = None, + url_scheme: str | None = None, + *args: t.Any, + **kwargs: t.Any, + ) -> None: + assert not (base_url or subdomain or url_scheme) or ( + base_url is not None + ) != bool( + subdomain or url_scheme + ), 'Cannot pass "subdomain" or "url_scheme" with "base_url".' + + if base_url is None: + http_host = app.config.get("SERVER_NAME") or "localhost" + app_root = app.config["APPLICATION_ROOT"] + + if subdomain: + http_host = f"{subdomain}.{http_host}" + + if url_scheme is None: + url_scheme = app.config["PREFERRED_URL_SCHEME"] + + url = urlsplit(path) + base_url = ( + f"{url.scheme or url_scheme}://{url.netloc or http_host}" + f"/{app_root.lstrip('/')}" + ) + path = url.path + + if url.query: + sep = b"?" if isinstance(url.query, bytes) else "?" + path += sep + url.query + + self.app = app + super().__init__(path, base_url, *args, **kwargs) + + def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: # type: ignore + """Serialize ``obj`` to a JSON-formatted string. + + The serialization will be configured according to the config associated + with this EnvironBuilder's ``app``. + """ + return self.app.json.dumps(obj, **kwargs) + + +_werkzeug_version = "" + + +def _get_werkzeug_version() -> str: + global _werkzeug_version + + if not _werkzeug_version: + _werkzeug_version = importlib.metadata.version("werkzeug") + + return _werkzeug_version + + +class FlaskClient(Client): + """Works like a regular Werkzeug test client but has knowledge about + Flask's contexts to defer the cleanup of the request context until + the end of a ``with`` block. For general information about how to + use this class refer to :class:`werkzeug.test.Client`. + + .. versionchanged:: 0.12 + `app.test_client()` includes preset default environment, which can be + set after instantiation of the `app.test_client()` object in + `client.environ_base`. + + Basic usage is outlined in the :doc:`/testing` chapter. + """ + + application: Flask + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super().__init__(*args, **kwargs) + self.preserve_context = False + self._new_contexts: list[t.ContextManager[t.Any]] = [] + self._context_stack = ExitStack() + self.environ_base = { + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}", + } + + @contextmanager + def session_transaction( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Iterator[SessionMixin]: + """When used in combination with a ``with`` statement this opens a + session transaction. This can be used to modify the session that + the test client uses. Once the ``with`` block is left the session is + stored back. + + :: + + with client.session_transaction() as session: + session['value'] = 42 + + Internally this is implemented by going through a temporary test + request context and since session handling could depend on + request variables this function accepts the same arguments as + :meth:`~flask.Flask.test_request_context` which are directly + passed through. + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + app = self.application + ctx = app.test_request_context(*args, **kwargs) + self._add_cookies_to_wsgi(ctx.request.environ) + + with ctx: + sess = app.session_interface.open_session(app, ctx.request) + + if sess is None: + raise RuntimeError("Session backend did not open a session.") + + yield sess + resp = app.response_class() + + if app.session_interface.is_null_session(sess): + return + + with ctx: + app.session_interface.save_session(app, sess, resp) + + self._update_cookies_from_response( + ctx.request.host.partition(":")[0], + ctx.request.path, + resp.headers.getlist("Set-Cookie"), + ) + + def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment: + out = {**self.environ_base, **other} + + if self.preserve_context: + out["werkzeug.debug.preserve_context"] = self._new_contexts.append + + return out + + def _request_from_builder_args( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> BaseRequest: + kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {})) + builder = EnvironBuilder(self.application, *args, **kwargs) + + try: + return builder.get_request() + finally: + builder.close() + + def open( + self, + *args: t.Any, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> TestResponse: + if args and isinstance( + args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest) + ): + if isinstance(args[0], werkzeug.test.EnvironBuilder): + builder = copy(args[0]) + builder.environ_base = self._copy_environ(builder.environ_base or {}) # type: ignore[arg-type] + request = builder.get_request() + elif isinstance(args[0], dict): + request = EnvironBuilder.from_environ( + args[0], app=self.application, environ_base=self._copy_environ({}) + ).get_request() + else: + # isinstance(args[0], BaseRequest) + request = copy(args[0]) + request.environ = self._copy_environ(request.environ) + else: + # request is None + request = self._request_from_builder_args(args, kwargs) + + # Pop any previously preserved contexts. This prevents contexts + # from being preserved across redirects or multiple requests + # within a single block. + self._context_stack.close() + + response = super().open( + request, + buffered=buffered, + follow_redirects=follow_redirects, + ) + response.json_module = self.application.json # type: ignore[assignment] + + # Re-push contexts that were preserved during the request. + while self._new_contexts: + cm = self._new_contexts.pop() + self._context_stack.enter_context(cm) + + return response + + def __enter__(self) -> FlaskClient: + if self.preserve_context: + raise RuntimeError("Cannot nest client invocations") + self.preserve_context = True + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.preserve_context = False + self._context_stack.close() + + +class FlaskCliRunner(CliRunner): + """A :class:`~click.testing.CliRunner` for testing a Flask app's + CLI commands. Typically created using + :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`. + """ + + def __init__(self, app: Flask, **kwargs: t.Any) -> None: + self.app = app + super().__init__(**kwargs) + + def invoke( # type: ignore + self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any + ) -> t.Any: + """Invokes a CLI command in an isolated environment. See + :meth:`CliRunner.invoke ` for + full method documentation. See :ref:`testing-cli` for examples. + + If the ``obj`` argument is not given, passes an instance of + :class:`~flask.cli.ScriptInfo` that knows how to load the Flask + app being tested. + + :param cli: Command object to invoke. Default is the app's + :attr:`~flask.app.Flask.cli` group. + :param args: List of strings to invoke the command with. + + :return: a :class:`~click.testing.Result` object. + """ + if cli is None: + cli = self.app.cli + + if "obj" not in kwargs: + kwargs["obj"] = ScriptInfo(create_app=lambda: self.app) + + return super().invoke(cli, args, **kwargs) + + +Filepath: githubCode\src\flask\typing.py: + +from __future__ import annotations + +import typing as t + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIApplication # noqa: F401 + from werkzeug.datastructures import Headers # noqa: F401 + from werkzeug.sansio.response import Response # noqa: F401 + +# The possible types that are directly convertible or are a Response object. +ResponseValue = t.Union[ + "Response", + str, + bytes, + t.List[t.Any], + # Only dict is actually accepted, but Mapping allows for TypedDict. + t.Mapping[str, t.Any], + t.Iterator[str], + t.Iterator[bytes], +] + +# the possible types for an individual HTTP header +# This should be a Union, but mypy doesn't pass unless it's a TypeVar. +HeaderValue = t.Union[str, t.List[str], t.Tuple[str, ...]] + +# the possible types for HTTP headers +HeadersValue = t.Union[ + "Headers", + t.Mapping[str, HeaderValue], + t.Sequence[t.Tuple[str, HeaderValue]], +] + +# The possible types returned by a route function. +ResponseReturnValue = t.Union[ + ResponseValue, + t.Tuple[ResponseValue, HeadersValue], + t.Tuple[ResponseValue, int], + t.Tuple[ResponseValue, int, HeadersValue], + "WSGIApplication", +] + +# Allow any subclass of werkzeug.Response, such as the one from Flask, +# as a callback argument. Using werkzeug.Response directly makes a +# callback annotated with flask.Response fail type checking. +ResponseClass = t.TypeVar("ResponseClass", bound="Response") + +AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named +AfterRequestCallable = t.Union[ + t.Callable[[ResponseClass], ResponseClass], + t.Callable[[ResponseClass], t.Awaitable[ResponseClass]], +] +BeforeFirstRequestCallable = t.Union[ + t.Callable[[], None], t.Callable[[], t.Awaitable[None]] +] +BeforeRequestCallable = t.Union[ + t.Callable[[], t.Optional[ResponseReturnValue]], + t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]], +] +ShellContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] +TeardownCallable = t.Union[ + t.Callable[[t.Optional[BaseException]], None], + t.Callable[[t.Optional[BaseException]], t.Awaitable[None]], +] +TemplateContextProcessorCallable = t.Union[ + t.Callable[[], t.Dict[str, t.Any]], + t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]], +] +TemplateFilterCallable = t.Callable[..., t.Any] +TemplateGlobalCallable = t.Callable[..., t.Any] +TemplateTestCallable = t.Callable[..., bool] +URLDefaultCallable = t.Callable[[str, t.Dict[str, t.Any]], None] +URLValuePreprocessorCallable = t.Callable[ + [t.Optional[str], t.Optional[t.Dict[str, t.Any]]], None +] + +# This should take Exception, but that either breaks typing the argument +# with a specific exception, or decorating multiple times with different +# exceptions (and using a union type on the argument). +# https://github.com/pallets/flask/issues/4095 +# https://github.com/pallets/flask/issues/4295 +# https://github.com/pallets/flask/issues/4297 +ErrorHandlerCallable = t.Union[ + t.Callable[[t.Any], ResponseReturnValue], + t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]], +] + +RouteCallable = t.Union[ + t.Callable[..., ResponseReturnValue], + t.Callable[..., t.Awaitable[ResponseReturnValue]], +] + + +Filepath: githubCode\src\flask\views.py: + +from __future__ import annotations + +import typing as t + +from . import typing as ft +from .globals import current_app +from .globals import request + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +http_method_funcs = frozenset( + ["get", "post", "head", "options", "delete", "put", "trace", "patch"] +) + + +class View: + """Subclass this class and override :meth:`dispatch_request` to + create a generic class-based view. Call :meth:`as_view` to create a + view function that creates an instance of the class with the given + arguments and calls its ``dispatch_request`` method with any URL + variables. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class Hello(View): + init_every_request = False + + def dispatch_request(self, name): + return f"Hello, {name}!" + + app.add_url_rule( + "/hello/", view_func=Hello.as_view("hello") + ) + + Set :attr:`methods` on the class to change what methods the view + accepts. + + Set :attr:`decorators` on the class to apply a list of decorators to + the generated view function. Decorators applied to the class itself + will not be applied to the generated view function! + + Set :attr:`init_every_request` to ``False`` for efficiency, unless + you need to store request-global data on ``self``. + """ + + #: The methods this view is registered for. Uses the same default + #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and + #: ``add_url_rule`` by default. + methods: t.ClassVar[t.Collection[str] | None] = None + + #: Control whether the ``OPTIONS`` method is handled automatically. + #: Uses the same default (``True``) as ``route`` and + #: ``add_url_rule`` by default. + provide_automatic_options: t.ClassVar[bool | None] = None + + #: A list of decorators to apply, in order, to the generated view + #: function. Remember that ``@decorator`` syntax is applied bottom + #: to top, so the first decorator in the list would be the bottom + #: decorator. + #: + #: .. versionadded:: 0.8 + decorators: t.ClassVar[list[t.Callable[[F], F]]] = [] + + #: Create a new instance of this view class for every request by + #: default. If a view subclass sets this to ``False``, the same + #: instance is used for every request. + #: + #: A single instance is more efficient, especially if complex setup + #: is done during init. However, storing data on ``self`` is no + #: longer safe across requests, and :data:`~flask.g` should be used + #: instead. + #: + #: .. versionadded:: 2.2 + init_every_request: t.ClassVar[bool] = True + + def dispatch_request(self) -> ft.ResponseReturnValue: + """The actual view function behavior. Subclasses must override + this and return a valid response. Any variables from the URL + rule are passed as keyword arguments. + """ + raise NotImplementedError() + + @classmethod + def as_view( + cls, name: str, *class_args: t.Any, **class_kwargs: t.Any + ) -> ft.RouteCallable: + """Convert the class into a view function that can be registered + for a route. + + By default, the generated view will create a new instance of the + view class for every request and call its + :meth:`dispatch_request` method. If the view class sets + :attr:`init_every_request` to ``False``, the same instance will + be used for every request. + + Except for ``name``, all other arguments passed to this method + are forwarded to the view class ``__init__`` method. + + .. versionchanged:: 2.2 + Added the ``init_every_request`` class attribute. + """ + if cls.init_every_request: + + def view(**kwargs: t.Any) -> ft.ResponseReturnValue: + self = view.view_class( # type: ignore[attr-defined] + *class_args, **class_kwargs + ) + return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return] + + else: + self = cls(*class_args, **class_kwargs) + + def view(**kwargs: t.Any) -> ft.ResponseReturnValue: + return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return] + + if cls.decorators: + view.__name__ = name + view.__module__ = cls.__module__ + for decorator in cls.decorators: + view = decorator(view) + + # We attach the view class to the view function for two reasons: + # first of all it allows us to easily figure out what class-based + # view this thing came from, secondly it's also used for instantiating + # the view class so you can actually replace it with something else + # for testing purposes and debugging. + view.view_class = cls # type: ignore + view.__name__ = name + view.__doc__ = cls.__doc__ + view.__module__ = cls.__module__ + view.methods = cls.methods # type: ignore + view.provide_automatic_options = cls.provide_automatic_options # type: ignore + return view + + +class MethodView(View): + """Dispatches request methods to the corresponding instance methods. + For example, if you implement a ``get`` method, it will be used to + handle ``GET`` requests. + + This can be useful for defining a REST API. + + :attr:`methods` is automatically set based on the methods defined on + the class. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class CounterAPI(MethodView): + def get(self): + return str(session.get("counter", 0)) + + def post(self): + session["counter"] = session.get("counter", 0) + 1 + return redirect(url_for("counter")) + + app.add_url_rule( + "/counter", view_func=CounterAPI.as_view("counter") + ) + """ + + def __init_subclass__(cls, **kwargs: t.Any) -> None: + super().__init_subclass__(**kwargs) + + if "methods" not in cls.__dict__: + methods = set() + + for base in cls.__bases__: + if getattr(base, "methods", None): + methods.update(base.methods) # type: ignore[attr-defined] + + for key in http_method_funcs: + if hasattr(cls, key): + methods.add(key.upper()) + + if methods: + cls.methods = methods + + def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue: + meth = getattr(self, request.method.lower(), None) + + # If the request method is HEAD and we don't have a handler for it + # retry with GET. + if meth is None and request.method == "HEAD": + meth = getattr(self, "get", None) + + assert meth is not None, f"Unimplemented method {request.method!r}" + return current_app.ensure_sync(meth)(**kwargs) # type: ignore[no-any-return] + + +Filepath: githubCode\src\flask\wrappers.py: + +from __future__ import annotations + +import typing as t + +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import HTTPException +from werkzeug.wrappers import Request as RequestBase +from werkzeug.wrappers import Response as ResponseBase + +from . import json +from .globals import current_app +from .helpers import _split_blueprint_path + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.routing import Rule + + +class Request(RequestBase): + """The request object used by default in Flask. Remembers the + matched endpoint and view arguments. + + It is what ends up as :class:`~flask.request`. If you want to replace + the request object used you can subclass this and set + :attr:`~flask.Flask.request_class` to your subclass. + + The request object is a :class:`~werkzeug.wrappers.Request` subclass and + provides all of the attributes Werkzeug defines plus a few Flask + specific ones. + """ + + json_module: t.Any = json + + #: The internal URL rule that matched the request. This can be + #: useful to inspect which methods are allowed for the URL from + #: a before/after handler (``request.url_rule.methods``) etc. + #: Though if the request's method was invalid for the URL rule, + #: the valid list is available in ``routing_exception.valid_methods`` + #: instead (an attribute of the Werkzeug exception + #: :exc:`~werkzeug.exceptions.MethodNotAllowed`) + #: because the request was never internally bound. + #: + #: .. versionadded:: 0.6 + url_rule: Rule | None = None + + #: A dict of view arguments that matched the request. If an exception + #: happened when matching, this will be ``None``. + view_args: dict[str, t.Any] | None = None + + #: If matching the URL failed, this is the exception that will be + #: raised / was raised as part of the request handling. This is + #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or + #: something similar. + routing_exception: HTTPException | None = None + + @property + def max_content_length(self) -> int | None: # type: ignore[override] + """Read-only view of the ``MAX_CONTENT_LENGTH`` config key.""" + if current_app: + return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return] + else: + return None + + @property + def endpoint(self) -> str | None: + """The endpoint that matched the request URL. + + This will be ``None`` if matching failed or has not been + performed yet. + + This in combination with :attr:`view_args` can be used to + reconstruct the same URL or a modified URL. + """ + if self.url_rule is not None: + return self.url_rule.endpoint + + return None + + @property + def blueprint(self) -> str | None: + """The registered name of the current blueprint. + + This will be ``None`` if the endpoint is not part of a + blueprint, or if URL matching failed or has not been performed + yet. + + This does not necessarily match the name the blueprint was + created with. It may have been nested, or registered with a + different name. + """ + endpoint = self.endpoint + + if endpoint is not None and "." in endpoint: + return endpoint.rpartition(".")[0] + + return None + + @property + def blueprints(self) -> list[str]: + """The registered names of the current blueprint upwards through + parent blueprints. + + This will be an empty list if there is no current blueprint, or + if URL matching failed. + + .. versionadded:: 2.0.1 + """ + name = self.blueprint + + if name is None: + return [] + + return _split_blueprint_path(name) + + def _load_form_data(self) -> None: + super()._load_form_data() + + # In debug mode we're replacing the files multidict with an ad-hoc + # subclass that raises a different error for key errors. + if ( + current_app + and current_app.debug + and self.mimetype != "multipart/form-data" + and not self.files + ): + from .debughelpers import attach_enctype_error_multidict + + attach_enctype_error_multidict(self) + + def on_json_loading_failed(self, e: ValueError | None) -> t.Any: + try: + return super().on_json_loading_failed(e) + except BadRequest as e: + if current_app and current_app.debug: + raise + + raise BadRequest() from e + + +class Response(ResponseBase): + """The response object that is used by default in Flask. Works like the + response object from Werkzeug but is set to have an HTML mimetype by + default. Quite often you don't have to create this object yourself because + :meth:`~flask.Flask.make_response` will take care of that for you. + + If you want to replace the response object used you can subclass this and + set :attr:`~flask.Flask.response_class` to your subclass. + + .. versionchanged:: 1.0 + JSON support is added to the response, like the request. This is useful + when testing to get the test client response data as JSON. + + .. versionchanged:: 1.0 + + Added :attr:`max_cookie_size`. + """ + + default_mimetype: str | None = "text/html" + + json_module = json + + autocorrect_location_header = False + + @property + def max_cookie_size(self) -> int: # type: ignore + """Read-only view of the :data:`MAX_COOKIE_SIZE` config key. + + See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in + Werkzeug's docs. + """ + if current_app: + return current_app.config["MAX_COOKIE_SIZE"] # type: ignore[no-any-return] + + # return Werkzeug's default when not in an app context + return super().max_cookie_size + + +Filepath: githubCode\src\flask\__init__.py: + +from __future__ import annotations + +import typing as t + +from . import json as json +from .app import Flask as Flask +from .blueprints import Blueprint as Blueprint +from .config import Config as Config +from .ctx import after_this_request as after_this_request +from .ctx import copy_current_request_context as copy_current_request_context +from .ctx import has_app_context as has_app_context +from .ctx import has_request_context as has_request_context +from .globals import current_app as current_app +from .globals import g as g +from .globals import request as request +from .globals import session as session +from .helpers import abort as abort +from .helpers import flash as flash +from .helpers import get_flashed_messages as get_flashed_messages +from .helpers import get_template_attribute as get_template_attribute +from .helpers import make_response as make_response +from .helpers import redirect as redirect +from .helpers import send_file as send_file +from .helpers import send_from_directory as send_from_directory +from .helpers import stream_with_context as stream_with_context +from .helpers import url_for as url_for +from .json import jsonify as jsonify +from .signals import appcontext_popped as appcontext_popped +from .signals import appcontext_pushed as appcontext_pushed +from .signals import appcontext_tearing_down as appcontext_tearing_down +from .signals import before_render_template as before_render_template +from .signals import got_request_exception as got_request_exception +from .signals import message_flashed as message_flashed +from .signals import request_finished as request_finished +from .signals import request_started as request_started +from .signals import request_tearing_down as request_tearing_down +from .signals import template_rendered as template_rendered +from .templating import render_template as render_template +from .templating import render_template_string as render_template_string +from .templating import stream_template as stream_template +from .templating import stream_template_string as stream_template_string +from .wrappers import Request as Request +from .wrappers import Response as Response + + +def __getattr__(name: str) -> t.Any: + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " Flask 3.1. Use feature detection or" + " 'importlib.metadata.version(\"flask\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("flask") + + raise AttributeError(name) + + +Filepath: githubCode\src\flask\__main__.py: + +from .cli import main + +main() + + +Filepath: githubCode\src\flask\json\provider.py: + +from __future__ import annotations + +import dataclasses +import decimal +import json +import typing as t +import uuid +import weakref +from datetime import date + +from werkzeug.http import http_date + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.sansio.response import Response + + from ..sansio.app import App + + +class JSONProvider: + """A standard set of JSON operations for an application. Subclasses + of this can be used to customize JSON behavior or use different + JSON libraries. + + To implement a provider for a specific library, subclass this base + class and implement at least :meth:`dumps` and :meth:`loads`. All + other methods have default implementations. + + To use a different provider, either subclass ``Flask`` and set + :attr:`~flask.Flask.json_provider_class` to a provider class, or set + :attr:`app.json ` to an instance of the class. + + :param app: An application instance. This will be stored as a + :class:`weakref.proxy` on the :attr:`_app` attribute. + + .. versionadded:: 2.2 + """ + + def __init__(self, app: App) -> None: + self._app: App = weakref.proxy(app) + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + :param obj: The data to serialize. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: May be passed to the underlying JSON library. + """ + fp.write(self.dumps(obj, **kwargs)) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + :param s: Text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + return self.loads(fp.read(), **kwargs) + + def _prepare_response_obj( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> t.Any: + if args and kwargs: + raise TypeError("app.json.response() takes either args or kwargs, not both") + + if not args and not kwargs: + return None + + if len(args) == 1: + return args[0] + + return args or kwargs + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. + + The :func:`~flask.json.jsonify` function calls this method for + the current application. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + return self._app.response_class(self.dumps(obj), mimetype="application/json") + + +def _default(o: t.Any) -> t.Any: + if isinstance(o, date): + return http_date(o) + + if isinstance(o, (decimal.Decimal, uuid.UUID)): + return str(o) + + if dataclasses and dataclasses.is_dataclass(o): + return dataclasses.asdict(o) + + if hasattr(o, "__html__"): + return str(o.__html__()) + + raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") + + +class DefaultJSONProvider(JSONProvider): + """Provide JSON operations using Python's built-in :mod:`json` + library. Serializes the following additional data types: + + - :class:`datetime.datetime` and :class:`datetime.date` are + serialized to :rfc:`822` strings. This is the same as the HTTP + date format. + - :class:`uuid.UUID` is serialized to a string. + - :class:`dataclasses.dataclass` is passed to + :func:`dataclasses.asdict`. + - :class:`~markupsafe.Markup` (or any object with a ``__html__`` + method) will call the ``__html__`` method to get a string. + """ + + default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment] + """Apply this function to any object that :meth:`json.dumps` does + not know how to serialize. It should return a valid JSON type or + raise a ``TypeError``. + """ + + ensure_ascii = True + """Replace non-ASCII characters with escape sequences. This may be + more compatible with some clients, but can be disabled for better + performance and size. + """ + + sort_keys = True + """Sort the keys in any serialized dicts. This may be useful for + some caching situations, but can be disabled for better performance. + When enabled, keys must all be strings, they are not converted + before sorting. + """ + + compact: bool | None = None + """If ``True``, or ``None`` out of debug mode, the :meth:`response` + output will not add indentation, newlines, or spaces. If ``False``, + or ``None`` in debug mode, it will use a non-compact representation. + """ + + mimetype = "application/json" + """The mimetype set in :meth:`response`.""" + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON to a string. + + Keyword arguments are passed to :func:`json.dumps`. Sets some + parameter defaults from the :attr:`default`, + :attr:`ensure_ascii`, and :attr:`sort_keys` attributes. + + :param obj: The data to serialize. + :param kwargs: Passed to :func:`json.dumps`. + """ + kwargs.setdefault("default", self.default) + kwargs.setdefault("ensure_ascii", self.ensure_ascii) + kwargs.setdefault("sort_keys", self.sort_keys) + return json.dumps(obj, **kwargs) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON from a string or bytes. + + :param s: Text or UTF-8 bytes. + :param kwargs: Passed to :func:`json.loads`. + """ + return json.loads(s, **kwargs) + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with it. The response mimetype + will be "application/json" and can be changed with + :attr:`mimetype`. + + If :attr:`compact` is ``False`` or debug mode is enabled, the + output will be formatted to be easier to read. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + dump_args: dict[str, t.Any] = {} + + if (self.compact is None and self._app.debug) or self.compact is False: + dump_args.setdefault("indent", 2) + else: + dump_args.setdefault("separators", (",", ":")) + + return self._app.response_class( + f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype + ) + + +Filepath: githubCode\src\flask\json\tag.py: + +""" +Tagged JSON +~~~~~~~~~~~ + +A compact representation for lossless serialization of non-standard JSON +types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this +to serialize the session data, but it may be useful in other places. It +can be extended to support other types. + +.. autoclass:: TaggedJSONSerializer + :members: + +.. autoclass:: JSONTag + :members: + +Let's see an example that adds support for +:class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so +to handle this we will dump the items as a list of ``[key, value]`` +pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to +identify the type. The session serializer processes dicts first, so +insert the new tag at the front of the order since ``OrderedDict`` must +be processed before ``dict``. + +.. code-block:: python + + from flask.json.tag import JSONTag + + class TagOrderedDict(JSONTag): + __slots__ = ('serializer',) + key = ' od' + + def check(self, value): + return isinstance(value, OrderedDict) + + def to_json(self, value): + return [[k, self.serializer.tag(v)] for k, v in iteritems(value)] + + def to_python(self, value): + return OrderedDict(value) + + app.session_interface.serializer.register(TagOrderedDict, index=0) +""" +from __future__ import annotations + +import typing as t +from base64 import b64decode +from base64 import b64encode +from datetime import datetime +from uuid import UUID + +from markupsafe import Markup +from werkzeug.http import http_date +from werkzeug.http import parse_date + +from ..json import dumps +from ..json import loads + + +class JSONTag: + """Base class for defining type tags for :class:`TaggedJSONSerializer`.""" + + __slots__ = ("serializer",) + + #: The tag to mark the serialized object with. If empty, this tag is + #: only used as an intermediate step during tagging. + key: str = "" + + def __init__(self, serializer: TaggedJSONSerializer) -> None: + """Create a tagger for the given serializer.""" + self.serializer = serializer + + def check(self, value: t.Any) -> bool: + """Check if the given value should be tagged by this tag.""" + raise NotImplementedError + + def to_json(self, value: t.Any) -> t.Any: + """Convert the Python object to an object that is a valid JSON type. + The tag will be added later.""" + raise NotImplementedError + + def to_python(self, value: t.Any) -> t.Any: + """Convert the JSON representation back to the correct type. The tag + will already be removed.""" + raise NotImplementedError + + def tag(self, value: t.Any) -> dict[str, t.Any]: + """Convert the value to a valid JSON type and add the tag structure + around it.""" + return {self.key: self.to_json(value)} + + +class TagDict(JSONTag): + """Tag for 1-item dicts whose only key matches a registered tag. + + Internally, the dict key is suffixed with `__`, and the suffix is removed + when deserializing. + """ + + __slots__ = () + key = " di" + + def check(self, value: t.Any) -> bool: + return ( + isinstance(value, dict) + and len(value) == 1 + and next(iter(value)) in self.serializer.tags + ) + + def to_json(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {f"{key}__": self.serializer.tag(value[key])} + + def to_python(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {key[:-2]: value[key]} + + +class PassDict(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, dict) + + def to_json(self, value: t.Any) -> t.Any: + # JSON objects may only have string keys, so don't bother tagging the + # key here. + return {k: self.serializer.tag(v) for k, v in value.items()} + + tag = to_json + + +class TagTuple(JSONTag): + __slots__ = () + key = " t" + + def check(self, value: t.Any) -> bool: + return isinstance(value, tuple) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + def to_python(self, value: t.Any) -> t.Any: + return tuple(value) + + +class PassList(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, list) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + tag = to_json + + +class TagBytes(JSONTag): + __slots__ = () + key = " b" + + def check(self, value: t.Any) -> bool: + return isinstance(value, bytes) + + def to_json(self, value: t.Any) -> t.Any: + return b64encode(value).decode("ascii") + + def to_python(self, value: t.Any) -> t.Any: + return b64decode(value) + + +class TagMarkup(JSONTag): + """Serialize anything matching the :class:`~markupsafe.Markup` API by + having a ``__html__`` method to the result of that method. Always + deserializes to an instance of :class:`~markupsafe.Markup`.""" + + __slots__ = () + key = " m" + + def check(self, value: t.Any) -> bool: + return callable(getattr(value, "__html__", None)) + + def to_json(self, value: t.Any) -> t.Any: + return str(value.__html__()) + + def to_python(self, value: t.Any) -> t.Any: + return Markup(value) + + +class TagUUID(JSONTag): + __slots__ = () + key = " u" + + def check(self, value: t.Any) -> bool: + return isinstance(value, UUID) + + def to_json(self, value: t.Any) -> t.Any: + return value.hex + + def to_python(self, value: t.Any) -> t.Any: + return UUID(value) + + +class TagDateTime(JSONTag): + __slots__ = () + key = " d" + + def check(self, value: t.Any) -> bool: + return isinstance(value, datetime) + + def to_json(self, value: t.Any) -> t.Any: + return http_date(value) + + def to_python(self, value: t.Any) -> t.Any: + return parse_date(value) + + +class TaggedJSONSerializer: + """Serializer that uses a tag system to compactly represent objects that + are not JSON types. Passed as the intermediate serializer to + :class:`itsdangerous.Serializer`. + + The following extra types are supported: + + * :class:`dict` + * :class:`tuple` + * :class:`bytes` + * :class:`~markupsafe.Markup` + * :class:`~uuid.UUID` + * :class:`~datetime.datetime` + """ + + __slots__ = ("tags", "order") + + #: Tag classes to bind when creating the serializer. Other tags can be + #: added later using :meth:`~register`. + default_tags = [ + TagDict, + PassDict, + TagTuple, + PassList, + TagBytes, + TagMarkup, + TagUUID, + TagDateTime, + ] + + def __init__(self) -> None: + self.tags: dict[str, JSONTag] = {} + self.order: list[JSONTag] = [] + + for cls in self.default_tags: + self.register(cls) + + def register( + self, + tag_class: type[JSONTag], + force: bool = False, + index: int | None = None, + ) -> None: + """Register a new tag with this serializer. + + :param tag_class: tag class to register. Will be instantiated with this + serializer instance. + :param force: overwrite an existing tag. If false (default), a + :exc:`KeyError` is raised. + :param index: index to insert the new tag in the tag order. Useful when + the new tag is a special case of an existing tag. If ``None`` + (default), the tag is appended to the end of the order. + + :raise KeyError: if the tag key is already registered and ``force`` is + not true. + """ + tag = tag_class(self) + key = tag.key + + if key: + if not force and key in self.tags: + raise KeyError(f"Tag '{key}' is already registered.") + + self.tags[key] = tag + + if index is None: + self.order.append(tag) + else: + self.order.insert(index, tag) + + def tag(self, value: t.Any) -> t.Any: + """Convert a value to a tagged representation if necessary.""" + for tag in self.order: + if tag.check(value): + return tag.tag(value) + + return value + + def untag(self, value: dict[str, t.Any]) -> t.Any: + """Convert a tagged representation back to the original type.""" + if len(value) != 1: + return value + + key = next(iter(value)) + + if key not in self.tags: + return value + + return self.tags[key].to_python(value[key]) + + def _untag_scan(self, value: t.Any) -> t.Any: + if isinstance(value, dict): + # untag each item recursively + value = {k: self._untag_scan(v) for k, v in value.items()} + # untag the dict itself + value = self.untag(value) + elif isinstance(value, list): + # untag each item recursively + value = [self._untag_scan(item) for item in value] + + return value + + def dumps(self, value: t.Any) -> str: + """Tag the value and dump it to a compact JSON string.""" + return dumps(self.tag(value), separators=(",", ":")) + + def loads(self, value: str) -> t.Any: + """Load data from a JSON string and deserialized any tagged objects.""" + return self._untag_scan(loads(value)) + + +Filepath: githubCode\src\flask\json\__init__.py: + +from __future__ import annotations + +import json as _json +import typing as t + +from ..globals import current_app +from .provider import _default + +if t.TYPE_CHECKING: # pragma: no cover + from ..wrappers import Response + + +def dumps(obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dumps() ` + method, otherwise it will use :func:`json.dumps`. + + :param obj: The data to serialize. + :param kwargs: Arguments passed to the ``dumps`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dumps``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.dumps(obj, **kwargs) + + kwargs.setdefault("default", _default) + return _json.dumps(obj, **kwargs) + + +def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dump() ` + method, otherwise it will use :func:`json.dump`. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: Arguments passed to the ``dump`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dump``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + Writing to a binary file, and the ``encoding`` argument, will be + removed in Flask 2.1. + """ + if current_app: + current_app.json.dump(obj, fp, **kwargs) + else: + kwargs.setdefault("default", _default) + _json.dump(obj, fp, **kwargs) + + +def loads(s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.loads() ` + method, otherwise it will use :func:`json.loads`. + + :param s: Text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``loads`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.loads``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The data must be a + string or UTF-8 bytes. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.loads(s, **kwargs) + + return _json.loads(s, **kwargs) + + +def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.load() ` + method, otherwise it will use :func:`json.load`. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``load`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.load``, allowing an app to override + the behavior. + + .. versionchanged:: 2.2 + The ``app`` parameter will be removed in Flask 2.3. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The file must be text + mode, or binary mode with UTF-8 bytes. + """ + if current_app: + return current_app.json.load(fp, **kwargs) + + return _json.load(fp, **kwargs) + + +def jsonify(*args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. A dict or list returned from a view will be converted to a + JSON response automatically without needing to call this. + + This requires an active request or application context, and calls + :meth:`app.json.response() `. + + In debug mode, the output is formatted with indentation to make it + easier to read. This may also be controlled by the provider. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + + .. versionchanged:: 2.2 + Calls ``current_app.json.response``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 0.11 + Added support for serializing top-level arrays. This was a + security risk in ancient browsers. See :ref:`security-json`. + + .. versionadded:: 0.2 + """ + return current_app.json.response(*args, **kwargs) # type: ignore[return-value] + + +Filepath: githubCode\src\flask\sansio\app.py: + +from __future__ import annotations + +import logging +import os +import sys +import typing as t +from datetime import timedelta +from itertools import chain + +from werkzeug.exceptions import Aborter +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequestKeyError +from werkzeug.routing import BuildError +from werkzeug.routing import Map +from werkzeug.routing import Rule +from werkzeug.sansio.response import Response +from werkzeug.utils import cached_property +from werkzeug.utils import redirect as _wz_redirect + +from .. import typing as ft +from ..config import Config +from ..config import ConfigAttribute +from ..ctx import _AppCtxGlobals +from ..helpers import _split_blueprint_path +from ..helpers import get_debug_flag +from ..json.provider import DefaultJSONProvider +from ..json.provider import JSONProvider +from ..logging import create_logger +from ..templating import DispatchingJinjaLoader +from ..templating import Environment +from .scaffold import _endpoint_from_view_func +from .scaffold import find_package +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.wrappers import Response as BaseResponse + + from ..testing import FlaskClient + from ..testing import FlaskCliRunner + from .blueprints import Blueprint + +T_shell_context_processor = t.TypeVar( + "T_shell_context_processor", bound=ft.ShellContextProcessorCallable +) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) +T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) +T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) + + +def _make_timedelta(value: timedelta | int | None) -> timedelta | None: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +class App(Scaffold): + """The flask object implements a WSGI application and acts as the central + object. It is passed the name of the module or package of the + application. Once it is created it will act as a central registry for + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 1.0 + The ``host_matching`` and ``static_host`` parameters were added. + + .. versionadded:: 1.0 + The ``subdomain_matching`` parameter was added. Subdomain + matching needs to be enabled manually now. Setting + :data:`SERVER_NAME` does not implicitly enable it. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: The folder with static files that is served at + ``static_url_path``. Relative to the application ``root_path`` + or an absolute path. Defaults to ``'static'``. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param host_matching: set ``url_map.host_matching`` attribute. + Defaults to False. + :param subdomain_matching: consider the subdomain relative to + :data:`SERVER_NAME` when matching routes. Defaults to False. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: The path to the root of the application files. + This should only be set manually when it can't be detected + automatically, such as for namespace packages. + """ + + #: The class of the object assigned to :attr:`aborter`, created by + #: :meth:`create_aborter`. That object is called by + #: :func:`flask.abort` to raise HTTP errors, and can be + #: called directly as well. + #: + #: Defaults to :class:`werkzeug.exceptions.Aborter`. + #: + #: .. versionadded:: 2.2 + aborter_class = Aborter + + #: The class that is used for the Jinja environment. + #: + #: .. versionadded:: 0.11 + jinja_environment = Environment + + #: The class that is used for the :data:`~flask.g` instance. + #: + #: Example use cases for a custom class: + #: + #: 1. Store arbitrary attributes on flask.g. + #: 2. Add a property for lazy per-request database connectors. + #: 3. Return None instead of AttributeError on unexpected attributes. + #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g. + #: + #: In Flask 0.9 this property was called `request_globals_class` but it + #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the + #: flask.g object is now application context scoped. + #: + #: .. versionadded:: 0.10 + app_ctx_globals_class = _AppCtxGlobals + + #: The class that is used for the ``config`` attribute of this app. + #: Defaults to :class:`~flask.Config`. + #: + #: Example use cases for a custom class: + #: + #: 1. Default values for certain config options. + #: 2. Access to config values through attributes in addition to keys. + #: + #: .. versionadded:: 0.11 + config_class = Config + + #: The testing flag. Set this to ``True`` to enable the test mode of + #: Flask extensions (and in the future probably also Flask itself). + #: For example this might activate test helpers that have an + #: additional runtime cost which should not be enabled by default. + #: + #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the + #: default it's implicitly enabled. + #: + #: This attribute can also be configured from the config with the + #: ``TESTING`` configuration key. Defaults to ``False``. + testing = ConfigAttribute[bool]("TESTING") + + #: If a secret key is set, cryptographic components can use this to + #: sign cookies and other things. Set this to a complex random value + #: when you want to use the secure cookie for instance. + #: + #: This attribute can also be configured from the config with the + #: :data:`SECRET_KEY` configuration key. Defaults to ``None``. + secret_key = ConfigAttribute[t.Union[str, bytes, None]]("SECRET_KEY") + + #: A :class:`~datetime.timedelta` which is used to set the expiration + #: date of a permanent session. The default is 31 days which makes a + #: permanent session survive for roughly one month. + #: + #: This attribute can also be configured from the config with the + #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to + #: ``timedelta(days=31)`` + permanent_session_lifetime = ConfigAttribute[timedelta]( + "PERMANENT_SESSION_LIFETIME", + get_converter=_make_timedelta, # type: ignore[arg-type] + ) + + json_provider_class: type[JSONProvider] = DefaultJSONProvider + """A subclass of :class:`~flask.json.provider.JSONProvider`. An + instance is created and assigned to :attr:`app.json` when creating + the app. + + The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses + Python's built-in :mod:`json` library. A different provider can use + a different JSON library. + + .. versionadded:: 2.2 + """ + + #: Options that are passed to the Jinja environment in + #: :meth:`create_jinja_environment`. Changing these options after + #: the environment is created (accessing :attr:`jinja_env`) will + #: have no effect. + #: + #: .. versionchanged:: 1.1.0 + #: This is a ``dict`` instead of an ``ImmutableDict`` to allow + #: easier configuration. + #: + jinja_options: dict[str, t.Any] = {} + + #: The rule object to use for URL rules created. This is used by + #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`. + #: + #: .. versionadded:: 0.7 + url_rule_class = Rule + + #: The map object to use for storing the URL rules and routing + #: configuration parameters. Defaults to :class:`werkzeug.routing.Map`. + #: + #: .. versionadded:: 1.1.0 + url_map_class = Map + + #: The :meth:`test_client` method creates an instance of this test + #: client class. Defaults to :class:`~flask.testing.FlaskClient`. + #: + #: .. versionadded:: 0.7 + test_client_class: type[FlaskClient] | None = None + + #: The :class:`~click.testing.CliRunner` subclass, by default + #: :class:`~flask.testing.FlaskCliRunner` that is used by + #: :meth:`test_cli_runner`. Its ``__init__`` method should take a + #: Flask app object as the first argument. + #: + #: .. versionadded:: 1.0 + test_cli_runner_class: type[FlaskCliRunner] | None = None + + default_config: dict[str, t.Any] + response_class: type[Response] + + def __init__( + self, + import_name: str, + static_url_path: str | None = None, + static_folder: str | os.PathLike[str] | None = "static", + static_host: str | None = None, + host_matching: bool = False, + subdomain_matching: bool = False, + template_folder: str | os.PathLike[str] | None = "templates", + instance_path: str | None = None, + instance_relative_config: bool = False, + root_path: str | None = None, + ): + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if instance_path is None: + instance_path = self.auto_find_instance_path() + elif not os.path.isabs(instance_path): + raise ValueError( + "If an instance path is provided it must be absolute." + " A relative path was given instead." + ) + + #: Holds the path to the instance folder. + #: + #: .. versionadded:: 0.8 + self.instance_path = instance_path + + #: The configuration dictionary as :class:`Config`. This behaves + #: exactly like a regular dictionary but supports additional methods + #: to load a config from files. + self.config = self.make_config(instance_relative_config) + + #: An instance of :attr:`aborter_class` created by + #: :meth:`make_aborter`. This is called by :func:`flask.abort` + #: to raise HTTP errors, and can be called directly as well. + #: + #: .. versionadded:: 2.2 + #: Moved from ``flask.abort``, which calls this object. + self.aborter = self.make_aborter() + + self.json: JSONProvider = self.json_provider_class(self) + """Provides access to JSON methods. Functions in ``flask.json`` + will call methods on this provider when the application context + is active. Used for handling JSON requests and responses. + + An instance of :attr:`json_provider_class`. Can be customized by + changing that attribute on a subclass, or by assigning to this + attribute afterwards. + + The default, :class:`~flask.json.provider.DefaultJSONProvider`, + uses Python's built-in :mod:`json` library. A different provider + can use a different JSON library. + + .. versionadded:: 2.2 + """ + + #: A list of functions that are called by + #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a + #: :exc:`~werkzeug.routing.BuildError`. Each function is called + #: with ``error``, ``endpoint`` and ``values``. If a function + #: returns ``None`` or raises a ``BuildError``, it is skipped. + #: Otherwise, its return value is returned by ``url_for``. + #: + #: .. versionadded:: 0.9 + self.url_build_error_handlers: list[ + t.Callable[[Exception, str, dict[str, t.Any]], str] + ] = [] + + #: A list of functions that are called when the application context + #: is destroyed. Since the application context is also torn down + #: if the request ends this is the place to store code that disconnects + #: from databases. + #: + #: .. versionadded:: 0.9 + self.teardown_appcontext_funcs: list[ft.TeardownCallable] = [] + + #: A list of shell context processor functions that should be run + #: when a shell context is created. + #: + #: .. versionadded:: 0.11 + self.shell_context_processors: list[ft.ShellContextProcessorCallable] = [] + + #: Maps registered blueprint names to blueprint objects. The + #: dict retains the order the blueprints were registered in. + #: Blueprints can be registered multiple times, this dict does + #: not track how often they were attached. + #: + #: .. versionadded:: 0.7 + self.blueprints: dict[str, Blueprint] = {} + + #: a place where extensions can store application specific state. For + #: example this is where an extension could store database engines and + #: similar things. + #: + #: The key must match the name of the extension module. For example in + #: case of a "Flask-Foo" extension in `flask_foo`, the key would be + #: ``'foo'``. + #: + #: .. versionadded:: 0.7 + self.extensions: dict[str, t.Any] = {} + + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. Example:: + #: + #: from werkzeug.routing import BaseConverter + #: + #: class ListConverter(BaseConverter): + #: def to_python(self, value): + #: return value.split(',') + #: def to_url(self, values): + #: return ','.join(super(ListConverter, self).to_url(value) + #: for value in values) + #: + #: app = Flask(__name__) + #: app.url_map.converters['list'] = ListConverter + self.url_map = self.url_map_class(host_matching=host_matching) + + self.subdomain_matching = subdomain_matching + + # tracks internally if the application already handled at least one + # request. + self._got_first_request = False + + # Set the name of the Click group in case someone wants to add + # the app's commands to another CLI tool. + self.cli.name = self.name + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_first_request: + raise AssertionError( + f"The setup method '{f_name}' can no longer be called" + " on the application. It has already handled its first" + " request, any changes will not be applied" + " consistently.\n" + "Make sure all imports, decorators, functions, etc." + " needed to set up the application are done before" + " running it." + ) + + @cached_property + def name(self) -> str: # type: ignore + """The name of the application. This is usually the import name + with the difference that it's guessed from the run file if the + import name is main. This name is used as a display name when + Flask needs the name of the application. It can be set and overridden + to change the value. + + .. versionadded:: 0.8 + """ + if self.import_name == "__main__": + fn: str | None = getattr(sys.modules["__main__"], "__file__", None) + if fn is None: + return "__main__" + return os.path.splitext(os.path.basename(fn))[0] + return self.import_name + + @cached_property + def logger(self) -> logging.Logger: + """A standard Python :class:`~logging.Logger` for the app, with + the same name as :attr:`name`. + + In debug mode, the logger's :attr:`~logging.Logger.level` will + be set to :data:`~logging.DEBUG`. + + If there are no handlers configured, a default handler will be + added. See :doc:`/logging` for more information. + + .. versionchanged:: 1.1.0 + The logger takes the same name as :attr:`name` rather than + hard-coding ``"flask.app"``. + + .. versionchanged:: 1.0.0 + Behavior was simplified. The logger is always named + ``"flask.app"``. The level is only set during configuration, + it doesn't check ``app.debug`` each time. Only one format is + used, not different ones depending on ``app.debug``. No + handlers are removed, and a handler is only added if no + handlers are already configured. + + .. versionadded:: 0.3 + """ + return create_logger(self) + + @cached_property + def jinja_env(self) -> Environment: + """The Jinja environment used to load templates. + + The environment is created the first time this property is + accessed. Changing :attr:`jinja_options` after that will have no + effect. + """ + return self.create_jinja_environment() + + def create_jinja_environment(self) -> Environment: + raise NotImplementedError() + + def make_config(self, instance_relative: bool = False) -> Config: + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + defaults = dict(self.default_config) + defaults["DEBUG"] = get_debug_flag() + return self.config_class(root_path, defaults) + + def make_aborter(self) -> Aborter: + """Create the object to assign to :attr:`aborter`. That object + is called by :func:`flask.abort` to raise HTTP errors, and can + be called directly as well. + + By default, this creates an instance of :attr:`aborter_class`, + which defaults to :class:`werkzeug.exceptions.Aborter`. + + .. versionadded:: 2.2 + """ + return self.aborter_class() + + def auto_find_instance_path(self) -> str: + """Tries to locate the instance path if it was not provided to the + constructor of the application class. It will basically calculate + the path to a folder named ``instance`` next to your main file or + the package. + + .. versionadded:: 0.8 + """ + prefix, package_path = find_package(self.import_name) + if prefix is None: + return os.path.join(package_path, "instance") + return os.path.join(prefix, "var", f"{self.name}-instance") + + def create_global_jinja_loader(self) -> DispatchingJinjaLoader: + """Creates the loader for the Jinja2 environment. Can be used to + override just the loader and keeping the rest unchanged. It's + discouraged to override this function. Instead one should override + the :meth:`jinja_loader` function instead. + + The global loader dispatches between the loaders of the application + and the individual blueprints. + + .. versionadded:: 0.7 + """ + return DispatchingJinjaLoader(self) + + def select_jinja_autoescape(self, filename: str) -> bool: + """Returns ``True`` if autoescaping should be active for the given + template name. If no template name is given, returns `True`. + + .. versionchanged:: 2.2 + Autoescaping is now enabled by default for ``.svg`` files. + + .. versionadded:: 0.5 + """ + if filename is None: + return True + return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg")) + + @property + def debug(self) -> bool: + """Whether debug mode is enabled. When using ``flask run`` to start the + development server, an interactive debugger will be shown for unhandled + exceptions, and the server will be reloaded when code changes. This maps to the + :data:`DEBUG` config key. It may not behave as expected if set late. + + **Do not enable debug mode when deploying in production.** + + Default: ``False`` + """ + return self.config["DEBUG"] # type: ignore[no-any-return] + + @debug.setter + def debug(self, value: bool) -> None: + self.config["DEBUG"] = value + + if self.config["TEMPLATES_AUTO_RELOAD"] is None: + self.jinja_env.auto_reload = value + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on the application. Keyword + arguments passed to this method will override the defaults set on the + blueprint. + + Calls the blueprint's :meth:`~flask.Blueprint.register` method after + recording the blueprint in the application's :attr:`blueprints`. + + :param blueprint: The blueprint to register. + :param url_prefix: Blueprint routes will be prefixed with this. + :param subdomain: Blueprint routes will match on this subdomain. + :param url_defaults: Blueprint routes will use these default values for + view arguments. + :param options: Additional keyword arguments are passed to + :class:`~flask.blueprints.BlueprintSetupState`. They can be + accessed in :meth:`~flask.Blueprint.record` callbacks. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 0.7 + """ + blueprint.register(self, options) + + def iter_blueprints(self) -> t.ValuesView[Blueprint]: + """Iterates over all blueprints by the order they were registered. + + .. versionadded:: 0.11 + """ + return self.blueprints.values() + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + options["endpoint"] = endpoint + methods = options.pop("methods", None) + + # if the methods are not given and the view_func object knows its + # methods we can use that instead. If neither exists, we go with + # a tuple of only ``GET`` as default. + if methods is None: + methods = getattr(view_func, "methods", None) or ("GET",) + if isinstance(methods, str): + raise TypeError( + "Allowed methods must be a list of strings, for" + ' example: @app.route(..., methods=["POST"])' + ) + methods = {item.upper() for item in methods} + + # Methods that should always be added + required_methods = set(getattr(view_func, "required_methods", ())) + + # starting with Flask 0.8 the view_func object can disable and + # force-enable the automatic options handling. + if provide_automatic_options is None: + provide_automatic_options = getattr( + view_func, "provide_automatic_options", None + ) + + if provide_automatic_options is None: + if "OPTIONS" not in methods: + provide_automatic_options = True + required_methods.add("OPTIONS") + else: + provide_automatic_options = False + + # Add the required methods now. + methods |= required_methods + + rule_obj = self.url_rule_class(rule, methods=methods, **options) + rule_obj.provide_automatic_options = provide_automatic_options # type: ignore[attr-defined] + + self.url_map.add(rule_obj) + if view_func is not None: + old_func = self.view_functions.get(endpoint) + if old_func is not None and old_func != view_func: + raise AssertionError( + "View function mapping is overwriting an existing" + f" endpoint function: {endpoint}" + ) + self.view_functions[endpoint] = view_func + + @setupmethod + def template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """A decorator that is used to register custom template filter. + You can specify a name for the filter, otherwise the function + name will be used. Example:: + + @app.template_filter() + def reverse(s): + return s[::-1] + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_filter( + self, f: ft.TemplateFilterCallable, name: str | None = None + ) -> None: + """Register a custom template filter. Works exactly like the + :meth:`template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + self.jinja_env.filters[name or f.__name__] = f + + @setupmethod + def template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: + """A decorator that is used to register custom template test. + You can specify a name for the test, otherwise the function + name will be used. Example:: + + @app.template_test() + def is_prime(n): + if n == 2: + return True + for i in range(2, int(math.ceil(math.sqrt(n))) + 1): + if n % i == 0: + return False + return True + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_test( + self, f: ft.TemplateTestCallable, name: str | None = None + ) -> None: + """Register a custom template test. Works exactly like the + :meth:`template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + self.jinja_env.tests[name or f.__name__] = f + + @setupmethod + def template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: + """A decorator that is used to register a custom template global function. + You can specify a name for the global function, otherwise the function + name will be used. Example:: + + @app.template_global() + def double(n): + return 2 * n + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_global( + self, f: ft.TemplateGlobalCallable, name: str | None = None + ) -> None: + """Register a custom template global function. Works exactly like the + :meth:`template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + self.jinja_env.globals[name or f.__name__] = f + + @setupmethod + def teardown_appcontext(self, f: T_teardown) -> T_teardown: + """Registers a function to be called when the application + context is popped. The application context is typically popped + after the request context for each request, at the end of CLI + commands, or after a manually pushed context ends. + + .. code-block:: python + + with app.app_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the app context is + made inactive. Since a request context typically also manages an + application context it would also be called when you pop a + request context. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + .. versionadded:: 0.9 + """ + self.teardown_appcontext_funcs.append(f) + return f + + @setupmethod + def shell_context_processor( + self, f: T_shell_context_processor + ) -> T_shell_context_processor: + """Registers a shell context processor function. + + .. versionadded:: 0.11 + """ + self.shell_context_processors.append(f) + return f + + def _find_error_handler( + self, e: Exception, blueprints: list[str] + ) -> ft.ErrorHandlerCallable | None: + """Return a registered error handler for an exception in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint handler for an exception class, app handler for an exception + class, or ``None`` if a suitable handler is not found. + """ + exc_class, code = self._get_exc_class_and_code(type(e)) + names = (*blueprints, None) + + for c in (code, None) if code is not None else (None,): + for name in names: + handler_map = self.error_handler_spec[name][c] + + if not handler_map: + continue + + for cls in exc_class.__mro__: + handler = handler_map.get(cls) + + if handler is not None: + return handler + return None + + def trap_http_exception(self, e: Exception) -> bool: + """Checks if an HTTP exception should be trapped or not. By default + this will return ``False`` for all exceptions except for a bad request + key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It + also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``. + + This is called for all HTTP exceptions raised by a view function. + If it returns ``True`` for any exception the error handler for this + exception is not called and it shows up as regular exception in the + traceback. This is helpful for debugging implicitly raised HTTP + exceptions. + + .. versionchanged:: 1.0 + Bad request errors are not trapped by default in debug mode. + + .. versionadded:: 0.8 + """ + if self.config["TRAP_HTTP_EXCEPTIONS"]: + return True + + trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"] + + # if unset, trap key errors in debug mode + if ( + trap_bad_request is None + and self.debug + and isinstance(e, BadRequestKeyError) + ): + return True + + if trap_bad_request: + return isinstance(e, BadRequest) + + return False + + def should_ignore_error(self, error: BaseException | None) -> bool: + """This is called to figure out if an error should be ignored + or not as far as the teardown system is concerned. If this + function returns ``True`` then the teardown handlers will not be + passed the error. + + .. versionadded:: 0.10 + """ + return False + + def redirect(self, location: str, code: int = 302) -> BaseResponse: + """Create a redirect response object. + + This is called by :func:`flask.redirect`, and can be called + directly as well. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + + .. versionadded:: 2.2 + Moved from ``flask.redirect``, which calls this method. + """ + return _wz_redirect( + location, + code=code, + Response=self.response_class, # type: ignore[arg-type] + ) + + def inject_url_defaults(self, endpoint: str, values: dict[str, t.Any]) -> None: + """Injects the URL defaults for the given endpoint directly into + the values dictionary passed. This is used internally and + automatically called on URL building. + + .. versionadded:: 0.7 + """ + names: t.Iterable[str | None] = (None,) + + # url_for may be called outside a request context, parse the + # passed endpoint instead of using request.blueprints. + if "." in endpoint: + names = chain( + names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0])) + ) + + for name in names: + if name in self.url_default_functions: + for func in self.url_default_functions[name]: + func(endpoint, values) + + def handle_url_build_error( + self, error: BuildError, endpoint: str, values: dict[str, t.Any] + ) -> str: + """Called by :meth:`.url_for` if a + :exc:`~werkzeug.routing.BuildError` was raised. If this returns + a value, it will be returned by ``url_for``, otherwise the error + will be re-raised. + + Each function in :attr:`url_build_error_handlers` is called with + ``error``, ``endpoint`` and ``values``. If a function returns + ``None`` or raises a ``BuildError``, it is skipped. Otherwise, + its return value is returned by ``url_for``. + + :param error: The active ``BuildError`` being handled. + :param endpoint: The endpoint being built. + :param values: The keyword arguments passed to ``url_for``. + """ + for handler in self.url_build_error_handlers: + try: + rv = handler(error, endpoint, values) + except BuildError as e: + # make error available outside except block + error = e + else: + if rv is not None: + return rv + + # Re-raise if called with an active exception, otherwise raise + # the passed in exception. + if error is sys.exc_info()[1]: + raise + + raise error + + +Filepath: githubCode\src\flask\sansio\blueprints.py: + +from __future__ import annotations + +import os +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from .. import typing as ft +from .scaffold import _endpoint_from_view_func +from .scaffold import _sentinel +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from .app import App + +DeferredSetupFunction = t.Callable[["BlueprintSetupState"], None] +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) +T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) +T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_context_processor = t.TypeVar( + "T_template_context_processor", bound=ft.TemplateContextProcessorCallable +) +T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) +T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) +T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) +T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) +T_url_value_preprocessor = t.TypeVar( + "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable +) + + +class BlueprintSetupState: + """Temporary holder object for registering a blueprint with the + application. An instance of this class is created by the + :meth:`~flask.Blueprint.make_setup_state` method and later passed + to all register callback functions. + """ + + def __init__( + self, + blueprint: Blueprint, + app: App, + options: t.Any, + first_registration: bool, + ) -> None: + #: a reference to the current application + self.app = app + + #: a reference to the blueprint that created this setup state. + self.blueprint = blueprint + + #: a dictionary with all options that were passed to the + #: :meth:`~flask.Flask.register_blueprint` method. + self.options = options + + #: as blueprints can be registered multiple times with the + #: application and not everything wants to be registered + #: multiple times on it, this attribute can be used to figure + #: out if the blueprint was registered in the past already. + self.first_registration = first_registration + + subdomain = self.options.get("subdomain") + if subdomain is None: + subdomain = self.blueprint.subdomain + + #: The subdomain that the blueprint should be active for, ``None`` + #: otherwise. + self.subdomain = subdomain + + url_prefix = self.options.get("url_prefix") + if url_prefix is None: + url_prefix = self.blueprint.url_prefix + #: The prefix that should be used for all URLs defined on the + #: blueprint. + self.url_prefix = url_prefix + + self.name = self.options.get("name", blueprint.name) + self.name_prefix = self.options.get("name_prefix", "") + + #: A dictionary with URL defaults that is added to each and every + #: URL that was defined with the blueprint. + self.url_defaults = dict(self.blueprint.url_values_defaults) + self.url_defaults.update(self.options.get("url_defaults", ())) + + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + **options: t.Any, + ) -> None: + """A helper method to register a rule (and optionally a view function) + to the application. The endpoint is automatically prefixed with the + blueprint's name. + """ + if self.url_prefix is not None: + if rule: + rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) + else: + rule = self.url_prefix + options.setdefault("subdomain", self.subdomain) + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + defaults = self.url_defaults + if "defaults" in options: + defaults = dict(defaults, **options.pop("defaults")) + + self.app.add_url_rule( + rule, + f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), + view_func, + defaults=defaults, + **options, + ) + + +class Blueprint(Scaffold): + """Represents a blueprint, a collection of routes and other + app-related functions that can be registered on a real application + later. + + A blueprint is an object that allows defining application functions + without requiring an application object ahead of time. It uses the + same decorators as :class:`~flask.Flask`, but defers the need for an + application by recording them for later registration. + + Decorating a function with a blueprint creates a deferred function + that is called with :class:`~flask.blueprints.BlueprintSetupState` + when the blueprint is registered on an application. + + See :doc:`/blueprints` for more information. + + :param name: The name of the blueprint. Will be prepended to each + endpoint name. + :param import_name: The name of the blueprint package, usually + ``__name__``. This helps locate the ``root_path`` for the + blueprint. + :param static_folder: A folder with static files that should be + served by the blueprint's static route. The path is relative to + the blueprint's root path. Blueprint static files are disabled + by default. + :param static_url_path: The url to serve static files from. + Defaults to ``static_folder``. If the blueprint does not have + a ``url_prefix``, the app's static route will take precedence, + and the blueprint's static files won't be accessible. + :param template_folder: A folder with templates that should be added + to the app's template search path. The path is relative to the + blueprint's root path. Blueprint templates are disabled by + default. Blueprint templates have a lower precedence than those + in the app's templates folder. + :param url_prefix: A path to prepend to all of the blueprint's URLs, + to make them distinct from the rest of the app's routes. + :param subdomain: A subdomain that blueprint routes will match on by + default. + :param url_defaults: A dict of default values that blueprint routes + will receive by default. + :param root_path: By default, the blueprint will automatically set + this based on ``import_name``. In certain situations this + automatic detection can fail, so the path can be specified + manually instead. + + .. versionchanged:: 1.1.0 + Blueprints have a ``cli`` group to register nested CLI commands. + The ``cli_group`` parameter controls the name of the group under + the ``flask`` command. + + .. versionadded:: 0.7 + """ + + _got_registered_once = False + + def __init__( + self, + name: str, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict[str, t.Any] | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore[assignment] + ): + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if not name: + raise ValueError("'name' may not be empty.") + + if "." in name: + raise ValueError("'name' may not contain a dot '.' character.") + + self.name = name + self.url_prefix = url_prefix + self.subdomain = subdomain + self.deferred_functions: list[DeferredSetupFunction] = [] + + if url_defaults is None: + url_defaults = {} + + self.url_values_defaults = url_defaults + self.cli_group = cli_group + self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = [] + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_registered_once: + raise AssertionError( + f"The setup method '{f_name}' can no longer be called on the blueprint" + f" '{self.name}'. It has already been registered at least once, any" + " changes will not be applied consistently.\n" + "Make sure all imports, decorators, functions, etc. needed to set up" + " the blueprint are done before registering it." + ) + + @setupmethod + def record(self, func: DeferredSetupFunction) -> None: + """Registers a function that is called when the blueprint is + registered on the application. This function is called with the + state as argument as returned by the :meth:`make_setup_state` + method. + """ + self.deferred_functions.append(func) + + @setupmethod + def record_once(self, func: DeferredSetupFunction) -> None: + """Works like :meth:`record` but wraps the function in another + function that will ensure the function is only called once. If the + blueprint is registered a second time on the application, the + function passed is not called. + """ + + def wrapper(state: BlueprintSetupState) -> None: + if state.first_registration: + func(state) + + self.record(update_wrapper(wrapper, func)) + + def make_setup_state( + self, app: App, options: dict[str, t.Any], first_registration: bool = False + ) -> BlueprintSetupState: + """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` + object that is later passed to the register callback functions. + Subclasses can override this to return a subclass of the setup state. + """ + return BlueprintSetupState(self, app, options, first_registration) + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on this blueprint. Keyword + arguments passed to this method will override the defaults set + on the blueprint. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 2.0 + """ + if blueprint is self: + raise ValueError("Cannot register a blueprint on itself") + self._blueprints.append((blueprint, options)) + + def register(self, app: App, options: dict[str, t.Any]) -> None: + """Called by :meth:`Flask.register_blueprint` to register all + views and callbacks registered on the blueprint with the + application. Creates a :class:`.BlueprintSetupState` and calls + each :meth:`record` callback with it. + + :param app: The application this blueprint is being registered + with. + :param options: Keyword arguments forwarded from + :meth:`~Flask.register_blueprint`. + + .. versionchanged:: 2.3 + Nested blueprints now correctly apply subdomains. + + .. versionchanged:: 2.1 + Registering the same blueprint with the same name multiple + times is an error. + + .. versionchanged:: 2.0.1 + Nested blueprints are registered with their dotted name. + This allows different blueprints with the same name to be + nested at different locations. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + """ + name_prefix = options.get("name_prefix", "") + self_name = options.get("name", self.name) + name = f"{name_prefix}.{self_name}".lstrip(".") + + if name in app.blueprints: + bp_desc = "this" if app.blueprints[name] is self else "a different" + existing_at = f" '{name}'" if self_name != name else "" + + raise ValueError( + f"The name '{self_name}' is already registered for" + f" {bp_desc} blueprint{existing_at}. Use 'name=' to" + f" provide a unique name." + ) + + first_bp_registration = not any(bp is self for bp in app.blueprints.values()) + first_name_registration = name not in app.blueprints + + app.blueprints[name] = self + self._got_registered_once = True + state = self.make_setup_state(app, options, first_bp_registration) + + if self.has_static_folder: + state.add_url_rule( + f"{self.static_url_path}/", + view_func=self.send_static_file, # type: ignore[attr-defined] + endpoint="static", + ) + + # Merge blueprint data into parent. + if first_bp_registration or first_name_registration: + self._merge_blueprint_funcs(app, name) + + for deferred in self.deferred_functions: + deferred(state) + + cli_resolved_group = options.get("cli_group", self.cli_group) + + if self.cli.commands: + if cli_resolved_group is None: + app.cli.commands.update(self.cli.commands) + elif cli_resolved_group is _sentinel: + self.cli.name = name + app.cli.add_command(self.cli) + else: + self.cli.name = cli_resolved_group + app.cli.add_command(self.cli) + + for blueprint, bp_options in self._blueprints: + bp_options = bp_options.copy() + bp_url_prefix = bp_options.get("url_prefix") + bp_subdomain = bp_options.get("subdomain") + + if bp_subdomain is None: + bp_subdomain = blueprint.subdomain + + if state.subdomain is not None and bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + "." + state.subdomain + elif bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + elif state.subdomain is not None: + bp_options["subdomain"] = state.subdomain + + if bp_url_prefix is None: + bp_url_prefix = blueprint.url_prefix + + if state.url_prefix is not None and bp_url_prefix is not None: + bp_options["url_prefix"] = ( + state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") + ) + elif bp_url_prefix is not None: + bp_options["url_prefix"] = bp_url_prefix + elif state.url_prefix is not None: + bp_options["url_prefix"] = state.url_prefix + + bp_options["name_prefix"] = name + blueprint.register(app, bp_options) + + def _merge_blueprint_funcs(self, app: App, name: str) -> None: + def extend( + bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + ) -> None: + for key, values in bp_dict.items(): + key = name if key is None else f"{name}.{key}" + parent_dict[key].extend(values) + + for key, value in self.error_handler_spec.items(): + key = name if key is None else f"{name}.{key}" + value = defaultdict( + dict, + { + code: {exc_class: func for exc_class, func in code_values.items()} + for code, code_values in value.items() + }, + ) + app.error_handler_spec[key] = value + + for endpoint, func in self.view_functions.items(): + app.view_functions[endpoint] = func + + extend(self.before_request_funcs, app.before_request_funcs) + extend(self.after_request_funcs, app.after_request_funcs) + extend( + self.teardown_request_funcs, + app.teardown_request_funcs, + ) + extend(self.url_default_functions, app.url_default_functions) + extend(self.url_value_preprocessors, app.url_value_preprocessors) + extend(self.template_context_processors, app.template_context_processors) + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for + full documentation. + + The URL rule is prefixed with the blueprint's URL prefix. The endpoint name, + used with :func:`url_for`, is prefixed with the blueprint's name. + """ + if endpoint and "." in endpoint: + raise ValueError("'endpoint' may not contain a dot '.' character.") + + if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: + raise ValueError("'view_func' name may not contain a dot '.' character.") + + self.record( + lambda s: s.add_url_rule( + rule, + endpoint, + view_func, + provide_automatic_options=provide_automatic_options, + **options, + ) + ) + + @setupmethod + def app_template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """Register a template filter, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_app_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_filter( + self, f: ft.TemplateFilterCallable, name: str | None = None + ) -> None: + """Register a template filter, available in any template rendered by the + application. Works like the :meth:`app_template_filter` decorator. Equivalent to + :meth:`.Flask.add_template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.filters[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: + """Register a template test, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_app_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_test( + self, f: ft.TemplateTestCallable, name: str | None = None + ) -> None: + """Register a template test, available in any template rendered by the + application. Works like the :meth:`app_template_test` decorator. Equivalent to + :meth:`.Flask.add_template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.tests[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: + """Register a template global, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_app_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_global( + self, f: ft.TemplateGlobalCallable, name: str | None = None + ) -> None: + """Register a template global, available in any template rendered by the + application. Works like the :meth:`app_template_global` decorator. Equivalent to + :meth:`.Flask.add_template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.globals[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def before_app_request(self, f: T_before_request) -> T_before_request: + """Like :meth:`before_request`, but before every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.before_request`. + """ + self.record_once( + lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def after_app_request(self, f: T_after_request) -> T_after_request: + """Like :meth:`after_request`, but after every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.after_request`. + """ + self.record_once( + lambda s: s.app.after_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def teardown_app_request(self, f: T_teardown) -> T_teardown: + """Like :meth:`teardown_request`, but after every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`. + """ + self.record_once( + lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_context_processor( + self, f: T_template_context_processor + ) -> T_template_context_processor: + """Like :meth:`context_processor`, but for templates rendered by every view, not + only by the blueprint. Equivalent to :meth:`.Flask.context_processor`. + """ + self.record_once( + lambda s: s.app.template_context_processors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_errorhandler( + self, code: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Like :meth:`errorhandler`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.errorhandler`. + """ + + def decorator(f: T_error_handler) -> T_error_handler: + def from_blueprint(state: BlueprintSetupState) -> None: + state.app.errorhandler(code)(f) + + self.record_once(from_blueprint) + return f + + return decorator + + @setupmethod + def app_url_value_preprocessor( + self, f: T_url_value_preprocessor + ) -> T_url_value_preprocessor: + """Like :meth:`url_value_preprocessor`, but for every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`. + """ + self.record_once( + lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Like :meth:`url_defaults`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.url_defaults`. + """ + self.record_once( + lambda s: s.app.url_default_functions.setdefault(None, []).append(f) + ) + return f + + +Filepath: githubCode\src\flask\sansio\scaffold.py: + +from __future__ import annotations + +import importlib.util +import os +import pathlib +import sys +import typing as t +from collections import defaultdict +from functools import update_wrapper + +import click +from jinja2 import BaseLoader +from jinja2 import FileSystemLoader +from werkzeug.exceptions import default_exceptions +from werkzeug.exceptions import HTTPException +from werkzeug.utils import cached_property + +from .. import typing as ft +from ..cli import AppGroup +from ..helpers import get_root_path +from ..templating import _default_template_ctx_processor + +# a singleton sentinel value for parameter defaults +_sentinel = object() + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) +T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) +T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_context_processor = t.TypeVar( + "T_template_context_processor", bound=ft.TemplateContextProcessorCallable +) +T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) +T_url_value_preprocessor = t.TypeVar( + "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable +) +T_route = t.TypeVar("T_route", bound=ft.RouteCallable) + + +def setupmethod(f: F) -> F: + f_name = f.__name__ + + def wrapper_func(self: Scaffold, *args: t.Any, **kwargs: t.Any) -> t.Any: + self._check_setup_finished(f_name) + return f(self, *args, **kwargs) + + return t.cast(F, update_wrapper(wrapper_func, f)) + + +class Scaffold: + """Common behavior shared between :class:`~flask.Flask` and + :class:`~flask.blueprints.Blueprint`. + + :param import_name: The import name of the module where this object + is defined. Usually :attr:`__name__` should be used. + :param static_folder: Path to a folder of static files to serve. + If this is set, a static route will be added. + :param static_url_path: URL prefix for the static route. + :param template_folder: Path to a folder containing template files. + for rendering. If this is set, a Jinja loader will be added. + :param root_path: The path that static, template, and resource files + are relative to. Typically not set, it is discovered based on + the ``import_name``. + + .. versionadded:: 2.0 + """ + + name: str + _static_folder: str | None = None + _static_url_path: str | None = None + + def __init__( + self, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + root_path: str | None = None, + ): + #: The name of the package or module that this object belongs + #: to. Do not change this once it is set by the constructor. + self.import_name = import_name + + self.static_folder = static_folder # type: ignore + self.static_url_path = static_url_path + + #: The path to the templates folder, relative to + #: :attr:`root_path`, to add to the template loader. ``None`` if + #: templates should not be added. + self.template_folder = template_folder + + if root_path is None: + root_path = get_root_path(self.import_name) + + #: Absolute path to the package on the filesystem. Used to look + #: up resources contained in the package. + self.root_path = root_path + + #: The Click command group for registering CLI commands for this + #: object. The commands are available from the ``flask`` command + #: once the application has been discovered and blueprints have + #: been registered. + self.cli: click.Group = AppGroup() + + #: A dictionary mapping endpoint names to view functions. + #: + #: To register a view function, use the :meth:`route` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.view_functions: dict[str, ft.RouteCallable] = {} + + #: A data structure of registered error handlers, in the format + #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is + #: the name of a blueprint the handlers are active for, or + #: ``None`` for all requests. The ``code`` key is the HTTP + #: status code for ``HTTPException``, or ``None`` for + #: other exceptions. The innermost dictionary maps exception + #: classes to handler functions. + #: + #: To register an error handler, use the :meth:`errorhandler` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.error_handler_spec: dict[ + ft.AppOrBlueprintKey, + dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]], + ] = defaultdict(lambda: defaultdict(dict)) + + #: A data structure of functions to call at the beginning of + #: each request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`before_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.before_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`after_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.after_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request even if an exception is raised, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`teardown_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.teardown_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.TeardownCallable] + ] = defaultdict(list) + + #: A data structure of functions to call to pass extra context + #: values when rendering templates, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`context_processor` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.template_context_processors: dict[ + ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable] + ] = defaultdict(list, {None: [_default_template_ctx_processor]}) + + #: A data structure of functions to call to modify the keyword + #: arguments passed to the view function, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the + #: :meth:`url_value_preprocessor` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_value_preprocessors: dict[ + ft.AppOrBlueprintKey, + list[ft.URLValuePreprocessorCallable], + ] = defaultdict(list) + + #: A data structure of functions to call to modify the keyword + #: arguments when generating URLs, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`url_defaults` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_default_functions: dict[ + ft.AppOrBlueprintKey, list[ft.URLDefaultCallable] + ] = defaultdict(list) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.name!r}>" + + def _check_setup_finished(self, f_name: str) -> None: + raise NotImplementedError + + @property + def static_folder(self) -> str | None: + """The absolute path to the configured static folder. ``None`` + if no static folder is set. + """ + if self._static_folder is not None: + return os.path.join(self.root_path, self._static_folder) + else: + return None + + @static_folder.setter + def static_folder(self, value: str | os.PathLike[str] | None) -> None: + if value is not None: + value = os.fspath(value).rstrip(r"\/") + + self._static_folder = value + + @property + def has_static_folder(self) -> bool: + """``True`` if :attr:`static_folder` is set. + + .. versionadded:: 0.5 + """ + return self.static_folder is not None + + @property + def static_url_path(self) -> str | None: + """The URL prefix that the static route will be accessible from. + + If it was not configured during init, it is derived from + :attr:`static_folder`. + """ + if self._static_url_path is not None: + return self._static_url_path + + if self.static_folder is not None: + basename = os.path.basename(self.static_folder) + return f"/{basename}".rstrip("/") + + return None + + @static_url_path.setter + def static_url_path(self, value: str | None) -> None: + if value is not None: + value = value.rstrip("/") + + self._static_url_path = value + + @cached_property + def jinja_loader(self) -> BaseLoader | None: + """The Jinja loader for this object's templates. By default this + is a class :class:`jinja2.loaders.FileSystemLoader` to + :attr:`template_folder` if it is set. + + .. versionadded:: 0.5 + """ + if self.template_folder is not None: + return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) + else: + return None + + def _method_route( + self, + method: str, + rule: str, + options: dict[str, t.Any], + ) -> t.Callable[[T_route], T_route]: + if "methods" in options: + raise TypeError("Use the 'route' decorator to use the 'methods' argument.") + + return self.route(rule, methods=[method], **options) + + @setupmethod + def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["GET"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("GET", rule, options) + + @setupmethod + def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["POST"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("POST", rule, options) + + @setupmethod + def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PUT"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PUT", rule, options) + + @setupmethod + def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["DELETE"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("DELETE", rule, options) + + @setupmethod + def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PATCH"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PATCH", rule, options) + + @setupmethod + def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Decorate a view function to register it with the given URL + rule and options. Calls :meth:`add_url_rule`, which has more + details about the implementation. + + .. code-block:: python + + @app.route("/") + def index(): + return "Hello, World!" + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and + ``OPTIONS`` are added automatically. + + :param rule: The URL rule string. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + + def decorator(f: T_route) -> T_route: + endpoint = options.pop("endpoint", None) + self.add_url_rule(rule, endpoint, f, **options) + return f + + return decorator + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a rule for routing incoming requests and building + URLs. The :meth:`route` decorator is a shortcut to call this + with the ``view_func`` argument. These are equivalent: + + .. code-block:: python + + @app.route("/") + def index(): + ... + + .. code-block:: python + + def index(): + ... + + app.add_url_rule("/", view_func=index) + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. An error + will be raised if a function has already been registered for the + endpoint. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is + always added automatically, and ``OPTIONS`` is added + automatically by default. + + ``view_func`` does not necessarily need to be passed, but if the + rule should participate in routing an endpoint name must be + associated with a view function at some point with the + :meth:`endpoint` decorator. + + .. code-block:: python + + app.add_url_rule("/", endpoint="index") + + @app.endpoint("index") + def index(): + ... + + If ``view_func`` has a ``required_methods`` attribute, those + methods are added to the passed and automatic methods. If it + has a ``provide_automatic_methods`` attribute, it is used as the + default if the parameter is not passed. + + :param rule: The URL rule string. + :param endpoint: The endpoint name to associate with the rule + and view function. Used when routing and building URLs. + Defaults to ``view_func.__name__``. + :param view_func: The view function to associate with the + endpoint name. + :param provide_automatic_options: Add the ``OPTIONS`` method and + respond to ``OPTIONS`` requests automatically. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + raise NotImplementedError + + @setupmethod + def endpoint(self, endpoint: str) -> t.Callable[[F], F]: + """Decorate a view function to register it for the given + endpoint. Used if a rule is added without a ``view_func`` with + :meth:`add_url_rule`. + + .. code-block:: python + + app.add_url_rule("/ex", endpoint="example") + + @app.endpoint("example") + def example(): + ... + + :param endpoint: The endpoint name to associate with the view + function. + """ + + def decorator(f: F) -> F: + self.view_functions[endpoint] = f + return f + + return decorator + + @setupmethod + def before_request(self, f: T_before_request) -> T_before_request: + """Register a function to run before each request. + + For example, this can be used to open a database connection, or + to load the logged in user from the session. + + .. code-block:: python + + @app.before_request + def load_user(): + if "user_id" in session: + g.user = db.session.get(session["user_id"]) + + The function will be called without any arguments. If it returns + a non-``None`` value, the value is handled as if it was the + return value from the view, and further request handling is + stopped. + + This is available on both app and blueprint objects. When used on an app, this + executes before every request. When used on a blueprint, this executes before + every request that the blueprint handles. To register with a blueprint and + execute before every request, use :meth:`.Blueprint.before_app_request`. + """ + self.before_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def after_request(self, f: T_after_request) -> T_after_request: + """Register a function to run after each request to this object. + + The function is called with the response object, and must return + a response object. This allows the functions to modify or + replace the response before it is sent. + + If a function raises an exception, any remaining + ``after_request`` functions will not be called. Therefore, this + should not be used for actions that must execute, such as to + close resources. Use :meth:`teardown_request` for that. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.after_app_request`. + """ + self.after_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_request(self, f: T_teardown) -> T_teardown: + """Register a function to be called when the request context is + popped. Typically this happens at the end of each request, but + contexts may be pushed manually as well during testing. + + .. code-block:: python + + with app.test_request_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the request context is + made inactive. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.teardown_app_request`. + """ + self.teardown_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def context_processor( + self, + f: T_template_context_processor, + ) -> T_template_context_processor: + """Registers a template context processor function. These functions run before + rendering a template. The keys of the returned dict are added as variables + available in the template. + + This is available on both app and blueprint objects. When used on an app, this + is called for every rendered template. When used on a blueprint, this is called + for templates rendered from the blueprint's views. To register with a blueprint + and affect every template, use :meth:`.Blueprint.app_context_processor`. + """ + self.template_context_processors[None].append(f) + return f + + @setupmethod + def url_value_preprocessor( + self, + f: T_url_value_preprocessor, + ) -> T_url_value_preprocessor: + """Register a URL value preprocessor function for all view + functions in the application. These functions will be called before the + :meth:`before_request` functions. + + The function can modify the values captured from the matched url before + they are passed to the view. For example, this can be used to pop a + common language code value and place it in ``g`` rather than pass it to + every view. + + The function is passed the endpoint name and values dict. The return + value is ignored. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_value_preprocessor`. + """ + self.url_value_preprocessors[None].append(f) + return f + + @setupmethod + def url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Callback function for URL defaults for all view functions of the + application. It's called with the endpoint and values and should + update the values passed in place. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_defaults`. + """ + self.url_default_functions[None].append(f) + return f + + @setupmethod + def errorhandler( + self, code_or_exception: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Register a function to handle errors by code or exception class. + + A decorator that is used to register a function given an + error code. Example:: + + @app.errorhandler(404) + def page_not_found(error): + return 'This page does not exist', 404 + + You can also register handlers for arbitrary exceptions:: + + @app.errorhandler(DatabaseError) + def special_exception_handler(error): + return 'Database connection failed', 500 + + This is available on both app and blueprint objects. When used on an app, this + can handle errors from every request. When used on a blueprint, this can handle + errors from requests that the blueprint handles. To register with a blueprint + and affect every request, use :meth:`.Blueprint.app_errorhandler`. + + .. versionadded:: 0.7 + Use :meth:`register_error_handler` instead of modifying + :attr:`error_handler_spec` directly, for application wide error + handlers. + + .. versionadded:: 0.7 + One can now additionally also register custom exception types + that do not necessarily have to be a subclass of the + :class:`~werkzeug.exceptions.HTTPException` class. + + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception + """ + + def decorator(f: T_error_handler) -> T_error_handler: + self.register_error_handler(code_or_exception, f) + return f + + return decorator + + @setupmethod + def register_error_handler( + self, + code_or_exception: type[Exception] | int, + f: ft.ErrorHandlerCallable, + ) -> None: + """Alternative error attach function to the :meth:`errorhandler` + decorator that is more straightforward to use for non decorator + usage. + + .. versionadded:: 0.7 + """ + exc_class, code = self._get_exc_class_and_code(code_or_exception) + self.error_handler_spec[None][code][exc_class] = f + + @staticmethod + def _get_exc_class_and_code( + exc_class_or_code: type[Exception] | int, + ) -> tuple[type[Exception], int | None]: + """Get the exception class being handled. For HTTP status codes + or ``HTTPException`` subclasses, return both the exception and + status code. + + :param exc_class_or_code: Any exception class, or an HTTP status + code as an integer. + """ + exc_class: type[Exception] + + if isinstance(exc_class_or_code, int): + try: + exc_class = default_exceptions[exc_class_or_code] + except KeyError: + raise ValueError( + f"'{exc_class_or_code}' is not a recognized HTTP" + " error code. Use a subclass of HTTPException with" + " that code instead." + ) from None + else: + exc_class = exc_class_or_code + + if isinstance(exc_class, Exception): + raise TypeError( + f"{exc_class!r} is an instance, not a class. Handlers" + " can only be registered for Exception classes or HTTP" + " error codes." + ) + + if not issubclass(exc_class, Exception): + raise ValueError( + f"'{exc_class.__name__}' is not a subclass of Exception." + " Handlers can only be registered for Exception classes" + " or HTTP error codes." + ) + + if issubclass(exc_class, HTTPException): + return exc_class, exc_class.code + else: + return exc_class, None + + +def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str: + """Internal helper that returns the default endpoint for a given + function. This always is the function name. + """ + assert view_func is not None, "expected view func if endpoint is not provided." + return view_func.__name__ + + +def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool: + # Path.is_relative_to doesn't exist until Python 3.9 + try: + path.relative_to(base) + return True + except ValueError: + return False + + +def _find_package_path(import_name: str) -> str: + """Find the path that contains the package or module.""" + root_mod_name, _, _ = import_name.partition(".") + + try: + root_spec = importlib.util.find_spec(root_mod_name) + + if root_spec is None: + raise ValueError("not found") + except (ImportError, ValueError): + # ImportError: the machinery told us it does not exist + # ValueError: + # - the module name was invalid + # - the module name is __main__ + # - we raised `ValueError` due to `root_spec` being `None` + return os.getcwd() + + if root_spec.submodule_search_locations: + if root_spec.origin is None or root_spec.origin == "namespace": + # namespace package + package_spec = importlib.util.find_spec(import_name) + + if package_spec is not None and package_spec.submodule_search_locations: + # Pick the path in the namespace that contains the submodule. + package_path = pathlib.Path( + os.path.commonpath(package_spec.submodule_search_locations) + ) + search_location = next( + location + for location in root_spec.submodule_search_locations + if _path_is_relative_to(package_path, location) + ) + else: + # Pick the first path. + search_location = root_spec.submodule_search_locations[0] + + return os.path.dirname(search_location) + else: + # package with __init__.py + return os.path.dirname(os.path.dirname(root_spec.origin)) + else: + # module + return os.path.dirname(root_spec.origin) # type: ignore[type-var, return-value] + + +def find_package(import_name: str) -> tuple[str | None, str]: + """Find the prefix that a package is installed under, and the path + that it would be imported from. + + The prefix is the directory containing the standard directory + hierarchy (lib, bin, etc.). If the package is not installed to the + system (:attr:`sys.prefix`) or a virtualenv (``site-packages``), + ``None`` is returned. + + The path is the entry in :attr:`sys.path` that contains the package + for import. If the package is not installed, it's assumed that the + package was imported from the current working directory. + """ + package_path = _find_package_path(import_name) + py_prefix = os.path.abspath(sys.prefix) + + # installed to the system + if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix): + return py_prefix, package_path + + site_parent, site_folder = os.path.split(package_path) + + # installed to a virtualenv + if site_folder.lower() == "site-packages": + parent, folder = os.path.split(site_parent) + + # Windows (prefix/lib/site-packages) + if folder.lower() == "lib": + return parent, package_path + + # Unix (prefix/lib/pythonX.Y/site-packages) + if os.path.basename(parent).lower() == "lib": + return os.path.dirname(parent), package_path + + # something else (prefix/site-packages) + return site_parent, package_path + + # not installed + return None, package_path + + +Filepath: githubCode\tests\typing\typing_app_decorators.py: + +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: + ... + + +Filepath: githubCode\tests\typing\typing_error_handler.py: + +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 "" + + +Filepath: githubCode\tests\typing\typing_route.py: + +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 "

Hello, World!

" + + +@app.route("/bytes") +def hello_bytes() -> bytes: + return b"

Hello, World!

" + + +@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/") +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/") +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"), +) + + +Filepath: githubCode\tests\test_apps\blueprintapp\__init__.py: + +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) + + +Filepath: githubCode\tests\test_apps\cliapp\app.py: + +from flask import Flask + +testapp = Flask("testapp") + + +Filepath: githubCode\tests\test_apps\cliapp\factory.py: + +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 + + +Filepath: githubCode\tests\test_apps\cliapp\importerrorapp.py: + +from flask import Flask + +raise ImportError() + +testapp = Flask("testapp") + + +Filepath: githubCode\tests\test_apps\cliapp\multiapp.py: + +from flask import Flask + +app1 = Flask("app1") +app2 = Flask("app2") + + +Filepath: githubCode\tests\test_apps\cliapp\__init__.py: + + + +Filepath: githubCode\tests\test_apps\helloworld\hello.py: + +from flask import Flask + +app = Flask(__name__) + + +@app.route("/") +def hello(): + return "Hello World!" + + +Filepath: githubCode\tests\test_apps\helloworld\wsgi.py: + +from hello import app # noqa: F401 + + +Filepath: githubCode\tests\test_apps\subdomaintestmodule\__init__.py: + +from flask import Module + +mod = Module(__name__, "foo", subdomain="foo") + + +Filepath: githubCode\tests\test_apps\blueprintapp\apps\__init__.py: + + + +Filepath: githubCode\tests\test_apps\blueprintapp\apps\admin\__init__.py: + +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") + + +Filepath: githubCode\tests\test_apps\blueprintapp\apps\frontend\__init__.py: + +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") + + +Filepath: githubCode\tests\test_apps\cliapp\inner1\__init__.py: + +from flask import Flask + +application = Flask(__name__) + + +Filepath: githubCode\tests\test_apps\cliapp\inner1\inner2\flask.py: + +from flask import Flask + +app = Flask(__name__) + + +Filepath: githubCode\tests\test_apps\cliapp\inner1\inner2\__init__.py: + + + +Filepath: Code\app.py: + +print("Hello world!") +