diff --git "a/Code.txt" "b/Code.txt" deleted file mode 100644--- "a/Code.txt" +++ /dev/null @@ -1,17855 +0,0 @@ -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!") -