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!")