codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import collections.abc as cabc4import os5import sys6import typing as t7import weakref8from datetime import timedelta9from inspect import iscoroutinefunction10from itertools import chain11from types import TracebackType12from urllib.parse import quote as _url_quote13 14import click15from werkzeug.datastructures import Headers16from werkzeug.datastructures import ImmutableDict17from werkzeug.exceptions import BadRequestKeyError18from werkzeug.exceptions import HTTPException19from werkzeug.exceptions import InternalServerError20from werkzeug.routing import BuildError21from werkzeug.routing import MapAdapter22from werkzeug.routing import RequestRedirect23from werkzeug.routing import RoutingException24from werkzeug.routing import Rule25from werkzeug.serving import is_running_from_reloader26from werkzeug.wrappers import Response as BaseResponse27from werkzeug.wsgi import get_host28 29from . import cli30from . import typing as ft31from .ctx import AppContext32from .ctx import RequestContext33from .globals import _cv_app34from .globals import _cv_request35from .globals import current_app36from .globals import g37from .globals import request38from .globals import request_ctx39from .globals import session40from .helpers import get_debug_flag41from .helpers import get_flashed_messages42from .helpers import get_load_dotenv43from .helpers import send_from_directory44from .sansio.app import App45from .sansio.scaffold import _sentinel46from .sessions import SecureCookieSessionInterface47from .sessions import SessionInterface48from .signals import appcontext_tearing_down49from .signals import got_request_exception50from .signals import request_finished51from .signals import request_started52from .signals import request_tearing_down53from .templating import Environment54from .wrappers import Request55from .wrappers import Response56 57if t.TYPE_CHECKING: # pragma: no cover58 from _typeshed.wsgi import StartResponse59 from _typeshed.wsgi import WSGIEnvironment60 61 from .testing import FlaskClient62 from .testing import FlaskCliRunner63 from .typing import HeadersValue64 65T_shell_context_processor = t.TypeVar(66 "T_shell_context_processor", bound=ft.ShellContextProcessorCallable67)68T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)69T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)70T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)71T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)72 73 74def _make_timedelta(value: timedelta | int | None) -> timedelta | None:75 if value is None or isinstance(value, timedelta):76 return value77 78 return timedelta(seconds=value)79 80 81class Flask(App):82 """The flask object implements a WSGI application and acts as the central83 object. It is passed the name of the module or package of the84 application. Once it is created it will act as a central registry for85 the view functions, the URL rules, template configuration and much more.86 87 The name of the package is used to resolve resources from inside the88 package or the folder the module is contained in depending on if the89 package parameter resolves to an actual python package (a folder with90 an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).91 92 For more information about resource loading, see :func:`open_resource`.93 94 Usually you create a :class:`Flask` instance in your main module or95 in the :file:`__init__.py` file of your package like this::96 97 from flask import Flask98 app = Flask(__name__)99 100 .. admonition:: About the First Parameter101 102 The idea of the first parameter is to give Flask an idea of what103 belongs to your application. This name is used to find resources104 on the filesystem, can be used by extensions to improve debugging105 information and a lot more.106 107 So it's important what you provide there. If you are using a single108 module, `__name__` is always the correct value. If you however are109 using a package, it's usually recommended to hardcode the name of110 your package there.111 112 For example if your application is defined in :file:`yourapplication/app.py`113 you should create it with one of the two versions below::114 115 app = Flask('yourapplication')116 app = Flask(__name__.split('.')[0])117 118 Why is that? The application will work even with `__name__`, thanks119 to how resources are looked up. However it will make debugging more120 painful. Certain extensions can make assumptions based on the121 import name of your application. For example the Flask-SQLAlchemy122 extension will look for the code in your application that triggered123 an SQL query in debug mode. If the import name is not properly set124 up, that debugging information is lost. (For example it would only125 pick up SQL queries in `yourapplication.app` and not126 `yourapplication.views.frontend`)127 128 .. versionadded:: 0.7129 The `static_url_path`, `static_folder`, and `template_folder`130 parameters were added.131 132 .. versionadded:: 0.8133 The `instance_path` and `instance_relative_config` parameters were134 added.135 136 .. versionadded:: 0.11137 The `root_path` parameter was added.138 139 .. versionadded:: 1.0140 The ``host_matching`` and ``static_host`` parameters were added.141 142 .. versionadded:: 1.0143 The ``subdomain_matching`` parameter was added. Subdomain144 matching needs to be enabled manually now. Setting145 :data:`SERVER_NAME` does not implicitly enable it.146 147 :param import_name: the name of the application package148 :param static_url_path: can be used to specify a different path for the149 static files on the web. Defaults to the name150 of the `static_folder` folder.151 :param static_folder: The folder with static files that is served at152 ``static_url_path``. Relative to the application ``root_path``153 or an absolute path. Defaults to ``'static'``.154 :param static_host: the host to use when adding the static route.155 Defaults to None. Required when using ``host_matching=True``156 with a ``static_folder`` configured.157 :param host_matching: set ``url_map.host_matching`` attribute.158 Defaults to False.159 :param subdomain_matching: consider the subdomain relative to160 :data:`SERVER_NAME` when matching routes. Defaults to False.161 :param template_folder: the folder that contains the templates that should162 be used by the application. Defaults to163 ``'templates'`` folder in the root path of the164 application.165 :param instance_path: An alternative instance path for the application.166 By default the folder ``'instance'`` next to the167 package or module is assumed to be the instance168 path.169 :param instance_relative_config: if set to ``True`` relative filenames170 for loading the config are assumed to171 be relative to the instance path instead172 of the application root.173 :param root_path: The path to the root of the application files.174 This should only be set manually when it can't be detected175 automatically, such as for namespace packages.176 """177 178 default_config = ImmutableDict(179 {180 "DEBUG": None,181 "TESTING": False,182 "PROPAGATE_EXCEPTIONS": None,183 "SECRET_KEY": None,184 "SECRET_KEY_FALLBACKS": None,185 "PERMANENT_SESSION_LIFETIME": timedelta(days=31),186 "USE_X_SENDFILE": False,187 "TRUSTED_HOSTS": None,188 "SERVER_NAME": None,189 "APPLICATION_ROOT": "/",190 "SESSION_COOKIE_NAME": "session",191 "SESSION_COOKIE_DOMAIN": None,192 "SESSION_COOKIE_PATH": None,193 "SESSION_COOKIE_HTTPONLY": True,194 "SESSION_COOKIE_SECURE": False,195 "SESSION_COOKIE_PARTITIONED": False,196 "SESSION_COOKIE_SAMESITE": None,197 "SESSION_REFRESH_EACH_REQUEST": True,198 "MAX_CONTENT_LENGTH": None,199 "MAX_FORM_MEMORY_SIZE": 500_000,200 "MAX_FORM_PARTS": 1_000,201 "SEND_FILE_MAX_AGE_DEFAULT": None,202 "TRAP_BAD_REQUEST_ERRORS": None,203 "TRAP_HTTP_EXCEPTIONS": False,204 "EXPLAIN_TEMPLATE_LOADING": False,205 "PREFERRED_URL_SCHEME": "http",206 "TEMPLATES_AUTO_RELOAD": None,207 "MAX_COOKIE_SIZE": 4093,208 "PROVIDE_AUTOMATIC_OPTIONS": True,209 }210 )211 212 #: The class that is used for request objects. See :class:`~flask.Request`213 #: for more information.214 request_class: type[Request] = Request215 216 #: The class that is used for response objects. See217 #: :class:`~flask.Response` for more information.218 response_class: type[Response] = Response219 220 #: the session interface to use. By default an instance of221 #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.222 #:223 #: .. versionadded:: 0.8224 session_interface: SessionInterface = SecureCookieSessionInterface()225 226 def __init__(227 self,228 import_name: str,229 static_url_path: str | None = None,230 static_folder: str | os.PathLike[str] | None = "static",231 static_host: str | None = None,232 host_matching: bool = False,233 subdomain_matching: bool = False,234 template_folder: str | os.PathLike[str] | None = "templates",235 instance_path: str | None = None,236 instance_relative_config: bool = False,237 root_path: str | None = None,238 ):239 super().__init__(240 import_name=import_name,241 static_url_path=static_url_path,242 static_folder=static_folder,243 static_host=static_host,244 host_matching=host_matching,245 subdomain_matching=subdomain_matching,246 template_folder=template_folder,247 instance_path=instance_path,248 instance_relative_config=instance_relative_config,249 root_path=root_path,250 )251 252 #: The Click command group for registering CLI commands for this253 #: object. The commands are available from the ``flask`` command254 #: once the application has been discovered and blueprints have255 #: been registered.256 self.cli = cli.AppGroup()257 258 # Set the name of the Click group in case someone wants to add259 # the app's commands to another CLI tool.260 self.cli.name = self.name261 262 # Add a static route using the provided static_url_path, static_host,263 # and static_folder if there is a configured static_folder.264 # Note we do this without checking if static_folder exists.265 # For one, it might be created while the server is running (e.g. during266 # development). Also, Google App Engine stores static files somewhere267 if self.has_static_folder:268 assert bool(static_host) == host_matching, (269 "Invalid static_host/host_matching combination"270 )271 # Use a weakref to avoid creating a reference cycle between the app272 # and the view function (see #3761).273 self_ref = weakref.ref(self)274 self.add_url_rule(275 f"{self.static_url_path}/<path:filename>",276 endpoint="static",277 host=static_host,278 view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore279 )280 281 def get_send_file_max_age(self, filename: str | None) -> int | None:282 """Used by :func:`send_file` to determine the ``max_age`` cache283 value for a given file path if it wasn't passed.284 285 By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from286 the configuration of :data:`~flask.current_app`. This defaults287 to ``None``, which tells the browser to use conditional requests288 instead of a timed cache, which is usually preferable.289 290 Note this is a duplicate of the same method in the Flask291 class.292 293 .. versionchanged:: 2.0294 The default configuration is ``None`` instead of 12 hours.295 296 .. versionadded:: 0.9297 """298 value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]299 300 if value is None:301 return None302 303 if isinstance(value, timedelta):304 return int(value.total_seconds())305 306 return value # type: ignore[no-any-return]307 308 def send_static_file(self, filename: str) -> Response:309 """The view function used to serve files from310 :attr:`static_folder`. A route is automatically registered for311 this view at :attr:`static_url_path` if :attr:`static_folder` is312 set.313 314 Note this is a duplicate of the same method in the Flask315 class.316 317 .. versionadded:: 0.5318 319 """320 if not self.has_static_folder:321 raise RuntimeError("'static_folder' must be set to serve static_files.")322 323 # send_file only knows to call get_send_file_max_age on the app,324 # call it here so it works for blueprints too.325 max_age = self.get_send_file_max_age(filename)326 return send_from_directory(327 t.cast(str, self.static_folder), filename, max_age=max_age328 )329 330 def open_resource(331 self, resource: str, mode: str = "rb", encoding: str | None = None332 ) -> t.IO[t.AnyStr]:333 """Open a resource file relative to :attr:`root_path` for reading.334 335 For example, if the file ``schema.sql`` is next to the file336 ``app.py`` where the ``Flask`` app is defined, it can be opened337 with:338 339 .. code-block:: python340 341 with app.open_resource("schema.sql") as f:342 conn.executescript(f.read())343 344 :param resource: Path to the resource relative to :attr:`root_path`.345 :param mode: Open the file in this mode. Only reading is supported,346 valid values are ``"r"`` (or ``"rt"``) and ``"rb"``.347 :param encoding: Open the file with this encoding when opening in text348 mode. This is ignored when opening in binary mode.349 350 .. versionchanged:: 3.1351 Added the ``encoding`` parameter.352 """353 if mode not in {"r", "rt", "rb"}:354 raise ValueError("Resources can only be opened for reading.")355 356 path = os.path.join(self.root_path, resource)357 358 if mode == "rb":359 return open(path, mode) # pyright: ignore360 361 return open(path, mode, encoding=encoding)362 363 def open_instance_resource(364 self, resource: str, mode: str = "rb", encoding: str | None = "utf-8"365 ) -> t.IO[t.AnyStr]:366 """Open a resource file relative to the application's instance folder367 :attr:`instance_path`. Unlike :meth:`open_resource`, files in the368 instance folder can be opened for writing.369 370 :param resource: Path to the resource relative to :attr:`instance_path`.371 :param mode: Open the file in this mode.372 :param encoding: Open the file with this encoding when opening in text373 mode. This is ignored when opening in binary mode.374 375 .. versionchanged:: 3.1376 Added the ``encoding`` parameter.377 """378 path = os.path.join(self.instance_path, resource)379 380 if "b" in mode:381 return open(path, mode)382 383 return open(path, mode, encoding=encoding)384 385 def create_jinja_environment(self) -> Environment:386 """Create the Jinja environment based on :attr:`jinja_options`387 and the various Jinja-related methods of the app. Changing388 :attr:`jinja_options` after this will have no effect. Also adds389 Flask-related globals and filters to the environment.390 391 .. versionchanged:: 0.11392 ``Environment.auto_reload`` set in accordance with393 ``TEMPLATES_AUTO_RELOAD`` configuration option.394 395 .. versionadded:: 0.5396 """397 options = dict(self.jinja_options)398 399 if "autoescape" not in options:400 options["autoescape"] = self.select_jinja_autoescape401 402 if "auto_reload" not in options:403 auto_reload = self.config["TEMPLATES_AUTO_RELOAD"]404 405 if auto_reload is None:406 auto_reload = self.debug407 408 options["auto_reload"] = auto_reload409 410 rv = self.jinja_environment(self, **options)411 rv.globals.update(412 url_for=self.url_for,413 get_flashed_messages=get_flashed_messages,414 config=self.config,415 # request, session and g are normally added with the416 # context processor for efficiency reasons but for imported417 # templates we also want the proxies in there.418 request=request,419 session=session,420 g=g,421 )422 rv.policies["json.dumps_function"] = self.json.dumps423 return rv424 425 def create_url_adapter(self, request: Request | None) -> MapAdapter | None:426 """Creates a URL adapter for the given request. The URL adapter427 is created at a point where the request context is not yet set428 up so the request is passed explicitly.429 430 .. versionchanged:: 3.1431 If :data:`SERVER_NAME` is set, it does not restrict requests to432 only that domain, for both ``subdomain_matching`` and433 ``host_matching``.434 435 .. versionchanged:: 1.0436 :data:`SERVER_NAME` no longer implicitly enables subdomain437 matching. Use :attr:`subdomain_matching` instead.438 439 .. versionchanged:: 0.9440 This can be called outside a request when the URL adapter is created441 for an application context.442 443 .. versionadded:: 0.6444 """445 if request is not None:446 if (trusted_hosts := self.config["TRUSTED_HOSTS"]) is not None:447 request.trusted_hosts = trusted_hosts448 449 # Check trusted_hosts here until bind_to_environ does.450 request.host = get_host(request.environ, request.trusted_hosts) # pyright: ignore451 subdomain = None452 server_name = self.config["SERVER_NAME"]453 454 if self.url_map.host_matching:455 # Don't pass SERVER_NAME, otherwise it's used and the actual456 # host is ignored, which breaks host matching.457 server_name = None458 elif not self.subdomain_matching:459 # Werkzeug doesn't implement subdomain matching yet. Until then,460 # disable it by forcing the current subdomain to the default, or461 # the empty string.462 subdomain = self.url_map.default_subdomain or ""463 464 return self.url_map.bind_to_environ(465 request.environ, server_name=server_name, subdomain=subdomain466 )467 468 # Need at least SERVER_NAME to match/build outside a request.469 if self.config["SERVER_NAME"] is not None:470 return self.url_map.bind(471 self.config["SERVER_NAME"],472 script_name=self.config["APPLICATION_ROOT"],473 url_scheme=self.config["PREFERRED_URL_SCHEME"],474 )475 476 return None477 478 def raise_routing_exception(self, request: Request) -> t.NoReturn:479 """Intercept routing exceptions and possibly do something else.480 481 In debug mode, intercept a routing redirect and replace it with482 an error if the body will be discarded.483 484 With modern Werkzeug this shouldn't occur, since it now uses a485 308 status which tells the browser to resend the method and486 body.487 488 .. versionchanged:: 2.1489 Don't intercept 307 and 308 redirects.490 491 :meta private:492 :internal:493 """494 if (495 not self.debug496 or not isinstance(request.routing_exception, RequestRedirect)497 or request.routing_exception.code in {307, 308}498 or request.method in {"GET", "HEAD", "OPTIONS"}499 ):500 raise request.routing_exception # type: ignore[misc]501 502 from .debughelpers import FormDataRoutingRedirect503 504 raise FormDataRoutingRedirect(request)505 506 def update_template_context(self, context: dict[str, t.Any]) -> None:507 """Update the template context with some commonly used variables.508 This injects request, session, config and g into the template509 context as well as everything template context processors want510 to inject. Note that the as of Flask 0.6, the original values511 in the context will not be overridden if a context processor512 decides to return a value with the same key.513 514 :param context: the context as a dictionary that is updated in place515 to add extra variables.516 """517 names: t.Iterable[str | None] = (None,)518 519 # A template may be rendered outside a request context.520 if request:521 names = chain(names, reversed(request.blueprints))522 523 # The values passed to render_template take precedence. Keep a524 # copy to re-apply after all context functions.525 orig_ctx = context.copy()526 527 for name in names:528 if name in self.template_context_processors:529 for func in self.template_context_processors[name]:530 context.update(self.ensure_sync(func)())531 532 context.update(orig_ctx)533 534 def make_shell_context(self) -> dict[str, t.Any]:535 """Returns the shell context for an interactive shell for this536 application. This runs all the registered shell context537 processors.538 539 .. versionadded:: 0.11540 """541 rv = {"app": self, "g": g}542 for processor in self.shell_context_processors:543 rv.update(processor())544 return rv545 546 def run(547 self,548 host: str | None = None,549 port: int | None = None,550 debug: bool | None = None,551 load_dotenv: bool = True,552 **options: t.Any,553 ) -> None:554 """Runs the application on a local development server.555 556 Do not use ``run()`` in a production setting. It is not intended to557 meet security and performance requirements for a production server.558 Instead, see :doc:`/deploying/index` for WSGI server recommendations.559 560 If the :attr:`debug` flag is set the server will automatically reload561 for code changes and show a debugger in case an exception happened.562 563 If you want to run the application in debug mode, but disable the564 code execution on the interactive debugger, you can pass565 ``use_evalex=False`` as parameter. This will keep the debugger's566 traceback screen active, but disable code execution.567 568 It is not recommended to use this function for development with569 automatic reloading as this is badly supported. Instead you should570 be using the :command:`flask` command line script's ``run`` support.571 572 .. admonition:: Keep in Mind573 574 Flask will suppress any server error with a generic error page575 unless it is in debug mode. As such to enable just the576 interactive debugger without the code reloading, you have to577 invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.578 Setting ``use_debugger`` to ``True`` without being in debug mode579 won't catch any exceptions because there won't be any to580 catch.581 582 :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to583 have the server available externally as well. Defaults to584 ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable585 if present.586 :param port: the port of the webserver. Defaults to ``5000`` or the587 port defined in the ``SERVER_NAME`` config variable if present.588 :param debug: if given, enable or disable debug mode. See589 :attr:`debug`.590 :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`591 files to set environment variables. Will also change the working592 directory to the directory containing the first file found.593 :param options: the options to be forwarded to the underlying Werkzeug594 server. See :func:`werkzeug.serving.run_simple` for more595 information.596 597 .. versionchanged:: 1.0598 If installed, python-dotenv will be used to load environment599 variables from :file:`.env` and :file:`.flaskenv` files.600 601 The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`.602 603 Threaded mode is enabled by default.604 605 .. versionchanged:: 0.10606 The default port is now picked from the ``SERVER_NAME``607 variable.608 """609 # Ignore this call so that it doesn't start another server if610 # the 'flask run' command is used.611 if os.environ.get("FLASK_RUN_FROM_CLI") == "true":612 if not is_running_from_reloader():613 click.secho(614 " * Ignoring a call to 'app.run()' that would block"615 " the current 'flask' CLI command.\n"616 " Only call 'app.run()' in an 'if __name__ =="617 ' "__main__"\' guard.',618 fg="red",619 )620 621 return622 623 if get_load_dotenv(load_dotenv):624 cli.load_dotenv()625 626 # if set, env var overrides existing value627 if "FLASK_DEBUG" in os.environ:628 self.debug = get_debug_flag()629 630 # debug passed to method overrides all other sources631 if debug is not None:632 self.debug = bool(debug)633 634 server_name = self.config.get("SERVER_NAME")635 sn_host = sn_port = None636 637 if server_name:638 sn_host, _, sn_port = server_name.partition(":")639 640 if not host:641 if sn_host:642 host = sn_host643 else:644 host = "127.0.0.1"645 646 if port or port == 0:647 port = int(port)648 elif sn_port:649 port = int(sn_port)650 else:651 port = 5000652 653 options.setdefault("use_reloader", self.debug)654 options.setdefault("use_debugger", self.debug)655 options.setdefault("threaded", True)656 657 cli.show_server_banner(self.debug, self.name)658 659 from werkzeug.serving import run_simple660 661 try:662 run_simple(t.cast(str, host), port, self, **options)663 finally:664 # reset the first request information if the development server665 # reset normally. This makes it possible to restart the server666 # without reloader and that stuff from an interactive shell.667 self._got_first_request = False668 669 def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient:670 """Creates a test client for this application. For information671 about unit testing head over to :doc:`/testing`.672 673 Note that if you are testing for assertions or exceptions in your674 application code, you must set ``app.testing = True`` in order for the675 exceptions to propagate to the test client. Otherwise, the exception676 will be handled by the application (not visible to the test client) and677 the only indication of an AssertionError or other exception will be a678 500 status code response to the test client. See the :attr:`testing`679 attribute. For example::680 681 app.testing = True682 client = app.test_client()683 684 The test client can be used in a ``with`` block to defer the closing down685 of the context until the end of the ``with`` block. This is useful if686 you want to access the context locals for testing::687 688 with app.test_client() as c:689 rv = c.get('/?vodka=42')690 assert request.args['vodka'] == '42'691 692 Additionally, you may pass optional keyword arguments that will then693 be passed to the application's :attr:`test_client_class` constructor.694 For example::695 696 from flask.testing import FlaskClient697 698 class CustomClient(FlaskClient):699 def __init__(self, *args, **kwargs):700 self._authentication = kwargs.pop("authentication")701 super(CustomClient,self).__init__( *args, **kwargs)702 703 app.test_client_class = CustomClient704 client = app.test_client(authentication='Basic ....')705 706 See :class:`~flask.testing.FlaskClient` for more information.707 708 .. versionchanged:: 0.4709 added support for ``with`` block usage for the client.710 711 .. versionadded:: 0.7712 The `use_cookies` parameter was added as well as the ability713 to override the client to be used by setting the714 :attr:`test_client_class` attribute.715 716 .. versionchanged:: 0.11717 Added `**kwargs` to support passing additional keyword arguments to718 the constructor of :attr:`test_client_class`.719 """720 cls = self.test_client_class721 if cls is None:722 from .testing import FlaskClient as cls723 return cls( # type: ignore724 self, self.response_class, use_cookies=use_cookies, **kwargs725 )726 727 def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner:728 """Create a CLI runner for testing CLI commands.729 See :ref:`testing-cli`.730 731 Returns an instance of :attr:`test_cli_runner_class`, by default732 :class:`~flask.testing.FlaskCliRunner`. The Flask app object is733 passed as the first argument.734 735 .. versionadded:: 1.0736 """737 cls = self.test_cli_runner_class738 739 if cls is None:740 from .testing import FlaskCliRunner as cls741 742 return cls(self, **kwargs) # type: ignore743 744 def handle_http_exception(745 self, e: HTTPException746 ) -> HTTPException | ft.ResponseReturnValue:747 """Handles an HTTP exception. By default this will invoke the748 registered error handlers and fall back to returning the749 exception as response.750 751 .. versionchanged:: 1.0.3752 ``RoutingException``, used internally for actions such as753 slash redirects during routing, is not passed to error754 handlers.755 756 .. versionchanged:: 1.0757 Exceptions are looked up by code *and* by MRO, so758 ``HTTPException`` subclasses can be handled with a catch-all759 handler for the base ``HTTPException``.760 761 .. versionadded:: 0.3762 """763 # Proxy exceptions don't have error codes. We want to always return764 # those unchanged as errors765 if e.code is None:766 return e767 768 # RoutingExceptions are used internally to trigger routing769 # actions, such as slash redirects raising RequestRedirect. They770 # are not raised or handled in user code.771 if isinstance(e, RoutingException):772 return e773 774 handler = self._find_error_handler(e, request.blueprints)775 if handler is None:776 return e777 return self.ensure_sync(handler)(e) # type: ignore[no-any-return]778 779 def handle_user_exception(780 self, e: Exception781 ) -> HTTPException | ft.ResponseReturnValue:782 """This method is called whenever an exception occurs that783 should be handled. A special case is :class:`~werkzeug784 .exceptions.HTTPException` which is forwarded to the785 :meth:`handle_http_exception` method. This function will either786 return a response value or reraise the exception with the same787 traceback.788 789 .. versionchanged:: 1.0790 Key errors raised from request data like ``form`` show the791 bad key in debug mode rather than a generic bad request792 message.793 794 .. versionadded:: 0.7795 """796 if isinstance(e, BadRequestKeyError) and (797 self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]798 ):799 e.show_exception = True800 801 if isinstance(e, HTTPException) and not self.trap_http_exception(e):802 return self.handle_http_exception(e)803 804 handler = self._find_error_handler(e, request.blueprints)805 806 if handler is None:807 raise808 809 return self.ensure_sync(handler)(e) # type: ignore[no-any-return]810 811 def handle_exception(self, e: Exception) -> Response:812 """Handle an exception that did not have an error handler813 associated with it, or that was raised from an error handler.814 This always causes a 500 ``InternalServerError``.815 816 Always sends the :data:`got_request_exception` signal.817 818 If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug819 mode, the error will be re-raised so that the debugger can820 display it. Otherwise, the original exception is logged, and821 an :exc:`~werkzeug.exceptions.InternalServerError` is returned.822 823 If an error handler is registered for ``InternalServerError`` or824 ``500``, it will be used. For consistency, the handler will825 always receive the ``InternalServerError``. The original826 unhandled exception is available as ``e.original_exception``.827 828 .. versionchanged:: 1.1.0829 Always passes the ``InternalServerError`` instance to the830 handler, setting ``original_exception`` to the unhandled831 error.832 833 .. versionchanged:: 1.1.0834 ``after_request`` functions and other finalization is done835 even for the default 500 response when there is no handler.836 837 .. versionadded:: 0.3838 """839 exc_info = sys.exc_info()840 got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e)841 propagate = self.config["PROPAGATE_EXCEPTIONS"]842 843 if propagate is None:844 propagate = self.testing or self.debug845 846 if propagate:847 # Re-raise if called with an active exception, otherwise848 # raise the passed in exception.849 if exc_info[1] is e:850 raise851 852 raise e853 854 self.log_exception(exc_info)855 server_error: InternalServerError | ft.ResponseReturnValue856 server_error = InternalServerError(original_exception=e)857 handler = self._find_error_handler(server_error, request.blueprints)858 859 if handler is not None:860 server_error = self.ensure_sync(handler)(server_error)861 862 return self.finalize_request(server_error, from_error_handler=True)863 864 def log_exception(865 self,866 exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),867 ) -> None:868 """Logs an exception. This is called by :meth:`handle_exception`869 if debugging is disabled and right before the handler is called.870 The default implementation logs the exception as error on the871 :attr:`logger`.872 873 .. versionadded:: 0.8874 """875 self.logger.error(876 f"Exception on {request.path} [{request.method}]", exc_info=exc_info877 )878 879 def dispatch_request(self) -> ft.ResponseReturnValue:880 """Does the request dispatching. Matches the URL and returns the881 return value of the view or error handler. This does not have to882 be a response object. In order to convert the return value to a883 proper response object, call :func:`make_response`.884 885 .. versionchanged:: 0.7886 This no longer does the exception handling, this code was887 moved to the new :meth:`full_dispatch_request`.888 """889 req = request_ctx.request890 if req.routing_exception is not None:891 self.raise_routing_exception(req)892 rule: Rule = req.url_rule # type: ignore[assignment]893 # if we provide automatic options for this URL and the894 # request came with the OPTIONS method, reply automatically895 if (896 getattr(rule, "provide_automatic_options", False)897 and req.method == "OPTIONS"898 ):899 return self.make_default_options_response()900 # otherwise dispatch to the handler for that endpoint901 view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]902 return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]903 904 def full_dispatch_request(self) -> Response:905 """Dispatches the request and on top of that performs request906 pre and postprocessing as well as HTTP exception catching and907 error handling.908 909 .. versionadded:: 0.7910 """911 self._got_first_request = True912 913 try:914 request_started.send(self, _async_wrapper=self.ensure_sync)915 rv = self.preprocess_request()916 if rv is None:917 rv = self.dispatch_request()918 except Exception as e:919 rv = self.handle_user_exception(e)920 return self.finalize_request(rv)921 922 def finalize_request(923 self,924 rv: ft.ResponseReturnValue | HTTPException,925 from_error_handler: bool = False,926 ) -> Response:927 """Given the return value from a view function this finalizes928 the request by converting it into a response and invoking the929 postprocessing functions. This is invoked for both normal930 request dispatching as well as error handlers.931 932 Because this means that it might be called as a result of a933 failure a special safe mode is available which can be enabled934 with the `from_error_handler` flag. If enabled, failures in935 response processing will be logged and otherwise ignored.936 937 :internal:938 """939 response = self.make_response(rv)940 try:941 response = self.process_response(response)942 request_finished.send(943 self, _async_wrapper=self.ensure_sync, response=response944 )945 except Exception:946 if not from_error_handler:947 raise948 self.logger.exception(949 "Request finalizing failed with an error while handling an error"950 )951 return response952 953 def make_default_options_response(self) -> Response:954 """This method is called to create the default ``OPTIONS`` response.955 This can be changed through subclassing to change the default956 behavior of ``OPTIONS`` responses.957 958 .. versionadded:: 0.7959 """960 adapter = request_ctx.url_adapter961 methods = adapter.allowed_methods() # type: ignore[union-attr]962 rv = self.response_class()963 rv.allow.update(methods)964 return rv965 966 def ensure_sync(self, func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:967 """Ensure that the function is synchronous for WSGI workers.968 Plain ``def`` functions are returned as-is. ``async def``969 functions are wrapped to run and wait for the response.970 971 Override this method to change how the app runs async views.972 973 .. versionadded:: 2.0974 """975 if iscoroutinefunction(func):976 return self.async_to_sync(func)977 978 return func979 980 def async_to_sync(981 self, func: t.Callable[..., t.Coroutine[t.Any, t.Any, t.Any]]982 ) -> t.Callable[..., t.Any]:983 """Return a sync function that will run the coroutine function.984 985 .. code-block:: python986 987 result = app.async_to_sync(func)(*args, **kwargs)988 989 Override this method to change how the app converts async code990 to be synchronously callable.991 992 .. versionadded:: 2.0993 """994 try:995 from asgiref.sync import async_to_sync as asgiref_async_to_sync996 except ImportError:997 raise RuntimeError(998 "Install Flask with the 'async' extra in order to use async views."999 ) from None1000 1001 return asgiref_async_to_sync(func)1002 1003 def url_for(1004 self,1005 /,1006 endpoint: str,1007 *,1008 _anchor: str | None = None,1009 _method: str | None = None,1010 _scheme: str | None = None,1011 _external: bool | None = None,1012 **values: t.Any,1013 ) -> str:1014 """Generate a URL to the given endpoint with the given values.1015 1016 This is called by :func:`flask.url_for`, and can be called1017 directly as well.1018 1019 An *endpoint* is the name of a URL rule, usually added with1020 :meth:`@app.route() <route>`, and usually the same name as the1021 view function. A route defined in a :class:`~flask.Blueprint`1022 will prepend the blueprint's name separated by a ``.`` to the1023 endpoint.1024 1025 In some cases, such as email messages, you want URLs to include1026 the scheme and domain, like ``https://example.com/hello``. When1027 not in an active request, URLs will be external by default, but1028 this requires setting :data:`SERVER_NAME` so Flask knows what1029 domain to use. :data:`APPLICATION_ROOT` and1030 :data:`PREFERRED_URL_SCHEME` should also be configured as1031 needed. This config is only used when not in an active request.1032 1033 Functions can be decorated with :meth:`url_defaults` to modify1034 keyword arguments before the URL is built.1035 1036 If building fails for some reason, such as an unknown endpoint1037 or incorrect values, the app's :meth:`handle_url_build_error`1038 method is called. If that returns a string, that is returned,1039 otherwise a :exc:`~werkzeug.routing.BuildError` is raised.1040 1041 :param endpoint: The endpoint name associated with the URL to1042 generate. If this starts with a ``.``, the current blueprint1043 name (if any) will be used.1044 :param _anchor: If given, append this as ``#anchor`` to the URL.1045 :param _method: If given, generate the URL associated with this1046 method for the endpoint.1047 :param _scheme: If given, the URL will have this scheme if it1048 is external.1049 :param _external: If given, prefer the URL to be internal1050 (False) or require it to be external (True). External URLs1051 include the scheme and domain. When not in an active1052 request, URLs are external by default.1053 :param values: Values to use for the variable parts of the URL1054 rule. Unknown keys are appended as query string arguments,1055 like ``?a=b&c=d``.1056 1057 .. versionadded:: 2.21058 Moved from ``flask.url_for``, which calls this method.1059 """1060 req_ctx = _cv_request.get(None)1061 1062 if req_ctx is not None:1063 url_adapter = req_ctx.url_adapter1064 blueprint_name = req_ctx.request.blueprint1065 1066 # If the endpoint starts with "." and the request matches a1067 # blueprint, the endpoint is relative to the blueprint.1068 if endpoint[:1] == ".":1069 if blueprint_name is not None:1070 endpoint = f"{blueprint_name}{endpoint}"1071 else:1072 endpoint = endpoint[1:]1073 1074 # When in a request, generate a URL without scheme and1075 # domain by default, unless a scheme is given.1076 if _external is None:1077 _external = _scheme is not None1078 else:1079 app_ctx = _cv_app.get(None)1080 1081 # If called by helpers.url_for, an app context is active,1082 # use its url_adapter. Otherwise, app.url_for was called1083 # directly, build an adapter.1084 if app_ctx is not None:1085 url_adapter = app_ctx.url_adapter1086 else:1087 url_adapter = self.create_url_adapter(None)1088 1089 if url_adapter is None:1090 raise RuntimeError(1091 "Unable to build URLs outside an active request"1092 " without 'SERVER_NAME' configured. Also configure"1093 " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as"1094 " needed."1095 )1096 1097 # When outside a request, generate a URL with scheme and1098 # domain by default.1099 if _external is None:1100 _external = True1101 1102 # It is an error to set _scheme when _external=False, in order1103 # to avoid accidental insecure URLs.1104 if _scheme is not None and not _external:1105 raise ValueError("When specifying '_scheme', '_external' must be True.")1106 1107 self.inject_url_defaults(endpoint, values)1108 1109 try:1110 rv = url_adapter.build( # type: ignore[union-attr]1111 endpoint,1112 values,1113 method=_method,1114 url_scheme=_scheme,1115 force_external=_external,1116 )1117 except BuildError as error:1118 values.update(1119 _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external1120 )1121 return self.handle_url_build_error(error, endpoint, values)1122 1123 if _anchor is not None:1124 _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@")1125 rv = f"{rv}#{_anchor}"1126 1127 return rv1128 1129 def make_response(self, rv: ft.ResponseReturnValue) -> Response:1130 """Convert the return value from a view function to an instance of1131 :attr:`response_class`.1132 1133 :param rv: the return value from the view function. The view function1134 must return a response. Returning ``None``, or the view ending1135 without returning, is not allowed. The following types are allowed1136 for ``view_rv``:1137 1138 ``str``1139 A response object is created with the string encoded to UTF-81140 as the body.1141 1142 ``bytes``1143 A response object is created with the bytes as the body.1144 1145 ``dict``1146 A dictionary that will be jsonify'd before being returned.1147 1148 ``list``1149 A list that will be jsonify'd before being returned.1150 1151 ``generator`` or ``iterator``1152 A generator that returns ``str`` or ``bytes`` to be1153 streamed as the response.1154 1155 ``tuple``1156 Either ``(body, status, headers)``, ``(body, status)``, or1157 ``(body, headers)``, where ``body`` is any of the other types1158 allowed here, ``status`` is a string or an integer, and1159 ``headers`` is a dictionary or a list of ``(key, value)``1160 tuples. If ``body`` is a :attr:`response_class` instance,1161 ``status`` overwrites the exiting value and ``headers`` are1162 extended.1163 1164 :attr:`response_class`1165 The object is returned unchanged.1166 1167 other :class:`~werkzeug.wrappers.Response` class1168 The object is coerced to :attr:`response_class`.1169 1170 :func:`callable`1171 The function is called as a WSGI application. The result is1172 used to create a response object.1173 1174 .. versionchanged:: 2.21175 A generator will be converted to a streaming response.1176 A list will be converted to a JSON response.1177 1178 .. versionchanged:: 1.11179 A dict will be converted to a JSON response.1180 1181 .. versionchanged:: 0.91182 Previously a tuple was interpreted as the arguments for the1183 response object.1184 """1185 1186 status: int | None = None1187 headers: HeadersValue | None = None1188 1189 # unpack tuple returns1190 if isinstance(rv, tuple):1191 len_rv = len(rv)1192 1193 # a 3-tuple is unpacked directly1194 if len_rv == 3:1195 rv, status, headers = rv # type: ignore[misc]1196 # decide if a 2-tuple has status or headers1197 elif len_rv == 2:1198 if isinstance(rv[1], (Headers, dict, tuple, list)):1199 rv, headers = rv # pyright: ignore1200 else: