From c7da6d74a89753405203e2ee7d19dda74b38b493 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 23:30:28 +0000 Subject: [PATCH 001/119] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.37.3 → v2.38.2](https://github.com/asottile/pyupgrade/compare/v2.37.3...v2.38.2) - [github.com/asottile/reorder_python_imports: v3.8.2 → v3.8.3](https://github.com/asottile/reorder_python_imports/compare/v3.8.2...v3.8.3) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c5731f9eb..da80a849e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,12 +3,12 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 + rev: v2.38.2 hooks: - id: pyupgrade args: ["--py36-plus"] - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.2 + rev: v3.8.3 hooks: - id: reorder-python-imports name: Reorder Python imports (src, tests) From 75e92090ee43642ad0608edc8bc5d4dc5e84dec8 Mon Sep 17 00:00:00 2001 From: TehBrian <32250137+TehBrian@users.noreply.github.com> Date: Tue, 4 Oct 2022 21:12:13 -0400 Subject: [PATCH 002/119] fix typo in quickstart --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 02dbc97835..f92bd24100 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -359,7 +359,7 @@ the application secure. Because of that Flask configures the `Jinja2 Templates can be used to generate any type of text file. For web applications, you'll primarily be generating HTML pages, but you can also generate markdown, plain text for -emails, any anything else. +emails, and anything else. For a reference to HTML, CSS, and other web APIs, use the `MDN Web Docs`_. From 5dfd2126a6d5210096ec6ba1ead471b79dc8bd17 Mon Sep 17 00:00:00 2001 From: Mat Steininger <32316796+mhsmathew@users.noreply.github.com> Date: Sat, 15 Oct 2022 18:34:37 -0400 Subject: [PATCH 003/119] Fixed inconsistent double spacing in docs/api.rst --- docs/api.rst | 90 ++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 880720b408..2b214582e7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,7 +3,7 @@ API .. module:: flask -This part of the documentation covers all the interfaces of Flask. For +This part of the documentation covers all the interfaces of Flask. For parts where Flask depends on external libraries, we document the most important right here and provide links to the canonical documentation. @@ -34,12 +34,12 @@ Incoming Request Data .. attribute:: request To access incoming request data, you can use the global `request` - object. Flask parses incoming request data for you and gives you - access to it through that global object. Internally Flask makes + object. Flask parses incoming request data for you and gives you + access to it through that global object. Internally Flask makes sure that you always get the correct data for the active thread if you are in a multithreaded environment. - This is a proxy. See :ref:`notes-on-proxies` for more information. + This is a proxy. See :ref:`notes-on-proxies` for more information. The request object is an instance of a :class:`~flask.Request`. @@ -69,7 +69,7 @@ To access the current session you can use the :class:`session` object: The session object works pretty much like an ordinary dict, with the difference that it keeps track of modifications. - This is a proxy. See :ref:`notes-on-proxies` for more information. + This is a proxy. See :ref:`notes-on-proxies` for more information. The following attributes are interesting: @@ -79,10 +79,10 @@ To access the current session you can use the :class:`session` object: .. attribute:: modified - ``True`` if the session object detected a modification. Be advised + ``True`` if the session object detected a modification. Be advised that modifications on mutable structures are not picked up automatically, in that situation you have to explicitly set the - attribute to ``True`` yourself. Here an example:: + attribute to ``True`` yourself. Here an example:: # this change is not picked up because a mutable object (here # a list) is changed. @@ -93,8 +93,8 @@ To access the current session you can use the :class:`session` object: .. attribute:: permanent If set to ``True`` the session lives for - :attr:`~flask.Flask.permanent_session_lifetime` seconds. The - default is 31 days. If set to ``False`` (which is the default) the + :attr:`~flask.Flask.permanent_session_lifetime` seconds. The + default is 31 days. If set to ``False`` (which is the default) the session will be deleted when the user closes the browser. @@ -155,9 +155,9 @@ Application Globals To share data that is valid for one request only from one function to another, a global variable is not good enough because it would break in -threaded environments. Flask provides you with a special object that +threaded environments. Flask provides you with a special object that ensures it is only valid for the active request and that will return -different values for each request. In a nutshell: it does the right +different values for each request. In a nutshell: it does the right thing, like it does for :class:`request` and :class:`session`. .. data:: g @@ -347,14 +347,14 @@ Signals .. data:: signals.signals_available - ``True`` if the signaling system is available. This is the case + ``True`` if the signaling system is available. This is the case when `blinker`_ is installed. The following signals exist in Flask: .. data:: template_rendered - This signal is sent when a template was successfully rendered. The + This signal is sent when a template was successfully rendered. The signal is invoked with the instance of the template as `template` and the context as dictionary (named `context`). @@ -388,7 +388,7 @@ The following signals exist in Flask: .. data:: request_started This signal is sent when the request context is set up, before - any request processing happens. Because the request context is already + any request processing happens. Because the request context is already bound, the subscriber can access the request with the standard global proxies such as :class:`~flask.request`. @@ -408,7 +408,7 @@ The following signals exist in Flask: Example subscriber:: def log_response(sender, response, **extra): - sender.logger.debug('Request context is about to close down. ' + sender.logger.debug('Request context is about to close down. ' 'Response: %s', response) from flask import request_finished @@ -445,8 +445,8 @@ The following signals exist in Flask: .. data:: request_tearing_down - This signal is sent when the request is tearing down. This is always - called, even if an exception is caused. Currently functions listening + This signal is sent when the request is tearing down. This is always + called, even if an exception is caused. Currently functions listening to this signal are called after the regular teardown handlers, but this is not something you can rely on. @@ -464,8 +464,8 @@ The following signals exist in Flask: .. data:: appcontext_tearing_down - This signal is sent when the app context is tearing down. This is always - called, even if an exception is caused. Currently functions listening + This signal is sent when the app context is tearing down. This is always + called, even if an exception is caused. Currently functions listening to this signal are called after the regular teardown handlers, but this is not something you can rely on. @@ -482,9 +482,9 @@ The following signals exist in Flask: .. data:: appcontext_pushed - This signal is sent when an application context is pushed. The sender - is the application. This is usually useful for unittests in order to - temporarily hook in information. For instance it can be used to + This signal is sent when an application context is pushed. The sender + is the application. This is usually useful for unittests in order to + temporarily hook in information. For instance it can be used to set a resource early onto the `g` object. Example usage:: @@ -511,8 +511,8 @@ The following signals exist in Flask: .. data:: appcontext_popped - This signal is sent when an application context is popped. The sender - is the application. This usually falls in line with the + This signal is sent when an application context is popped. The sender + is the application. This usually falls in line with the :data:`appcontext_tearing_down` signal. .. versionadded:: 0.10 @@ -520,7 +520,7 @@ The following signals exist in Flask: .. data:: message_flashed - This signal is sent when the application is flashing a message. The + This signal is sent when the application is flashing a message. The messages is sent as `message` keyword argument and the category as `category`. @@ -538,7 +538,7 @@ The following signals exist in Flask: .. class:: signals.Namespace An alias for :class:`blinker.base.Namespace` if blinker is available, - otherwise a dummy class that creates fake signals. This class is + otherwise a dummy class that creates fake signals. This class is available for Flask extensions that want to provide the same fallback system as Flask itself. @@ -579,7 +579,7 @@ Generally there are three ways to define rules for the routing system: which is exposed as :attr:`flask.Flask.url_map`. Variable parts in the route can be specified with angular brackets -(``/user/``). By default a variable part in the URL accepts any +(``/user/``). By default a variable part in the URL accepts any string without a slash however a different converter can be specified as well by using ````. @@ -613,7 +613,7 @@ Here are some examples:: pass An important detail to keep in mind is how Flask deals with trailing -slashes. The idea is to keep each URL unique so the following rules +slashes. The idea is to keep each URL unique so the following rules apply: 1. If a rule ends with a slash and is requested without a slash by the @@ -622,11 +622,11 @@ apply: 2. If a rule does not end with a trailing slash and the user requests the page with a trailing slash, a 404 not found is raised. -This is consistent with how web servers deal with static files. This +This is consistent with how web servers deal with static files. This also makes it possible to use relative link targets safely. -You can also define multiple rules for the same function. They have to be -unique however. Defaults can also be specified. Here for example is a +You can also define multiple rules for the same function. They have to be +unique however. Defaults can also be specified. Here for example is a definition for a URL that accepts an optional page:: @app.route('/users/', defaults={'page': 1}) @@ -649,33 +649,33 @@ can't preserve form data. :: pass Here are the parameters that :meth:`~flask.Flask.route` and -:meth:`~flask.Flask.add_url_rule` accept. The only difference is that +:meth:`~flask.Flask.add_url_rule` accept. The only difference is that with the route parameter the view function is defined with the decorator instead of the `view_func` parameter. =============== ========================================================== `rule` the URL rule as string -`endpoint` the endpoint for the registered URL rule. Flask itself +`endpoint` the endpoint for the registered URL rule. Flask itself assumes that the name of the view function is the name of the endpoint if not explicitly stated. `view_func` the function to call when serving a request to the - provided endpoint. If this is not provided one can + provided endpoint. If this is not provided one can specify the function later by storing it in the :attr:`~flask.Flask.view_functions` dictionary with the endpoint as key. -`defaults` A dictionary with defaults for this rule. See the +`defaults` A dictionary with defaults for this rule. See the example above for how defaults work. `subdomain` specifies the rule for the subdomain in case subdomain - matching is in use. If not specified the default + matching is in use. If not specified the default subdomain is assumed. `**options` the options to be forwarded to the underlying - :class:`~werkzeug.routing.Rule` object. A change to - Werkzeug is handling of method options. methods is a list + :class:`~werkzeug.routing.Rule` object. A change to + Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (``GET``, ``POST`` - etc.). By default a rule just listens for ``GET`` (and - implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is + etc.). By default a rule just listens for ``GET`` (and + implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is implicitly added and handled by the standard request - handling. They have to be specified as keyword arguments. + handling. They have to be specified as keyword arguments. =============== ========================================================== @@ -687,19 +687,19 @@ customize behavior the view function would normally not have control over. The following attributes can be provided optionally to either override some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior: -- `__name__`: The name of a function is by default used as endpoint. If - endpoint is provided explicitly this value is used. Additionally this +- `__name__`: The name of a function is by default used as endpoint. If + endpoint is provided explicitly this value is used. Additionally this will be prefixed with the name of the blueprint by default which cannot be customized from the function itself. - `methods`: If methods are not provided when the URL rule is added, Flask will look on the view function object itself if a `methods` - attribute exists. If it does, it will pull the information for the + attribute exists. If it does, it will pull the information for the methods from there. - `provide_automatic_options`: if this attribute is set Flask will either force enable or disable the automatic implementation of the - HTTP ``OPTIONS`` response. This can be useful when working with + HTTP ``OPTIONS`` response. This can be useful when working with decorators that want to customize the ``OPTIONS`` response on a per-view basis. From cc66213e579d6b35d9951c21b685d0078f373c44 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 30 Oct 2022 08:55:51 -0600 Subject: [PATCH 004/119] Add .svg to select_jinja_autoescape (#4840) As SVG files are a type of XML file and are similar in nearly all aspects to XML, .svg should also be autoescaped. --- CHANGES.rst | 3 +++ docs/templating.rst | 2 +- src/flask/app.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6c3ff32c97..c66bf7b86f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,9 @@ Version 2.2.3 Unreleased +- Autoescaping is now enabled by default for ``.svg`` files. Inside + templates this behavior can be changed with the ``autoescape`` tag. + :issue:`4831` Version 2.2.2 ------------- diff --git a/docs/templating.rst b/docs/templating.rst index 3cda995e44..f497de7333 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -18,7 +18,7 @@ Jinja Setup Unless customized, Jinja2 is configured by Flask as follows: - autoescaping is enabled for all templates ending in ``.html``, - ``.htm``, ``.xml`` as well as ``.xhtml`` when using + ``.htm``, ``.xml``, ``.xhtml``, as well as ``.svg`` when using :func:`~flask.templating.render_template`. - autoescaping is enabled for all strings when using :func:`~flask.templating.render_template_string`. diff --git a/src/flask/app.py b/src/flask/app.py index ce4dcf6a7d..aa5bd3ccea 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -961,11 +961,14 @@ 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")) + return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg")) def update_template_context(self, context: dict) -> None: """Update the template context with some commonly used variables. From 9daddd12719a098ff33556fcc34c0b7cf33120ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:06:23 +0000 Subject: [PATCH 005/119] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v2.38.2 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v2.38.2...v3.2.0) - [github.com/asottile/reorder_python_imports: v3.8.3 → v3.9.0](https://github.com/asottile/reorder_python_imports/compare/v3.8.3...v3.9.0) - [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da80a849e5..8fbb474ec0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,12 +3,12 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.38.2 + rev: v3.2.0 hooks: - id: pyupgrade args: ["--py36-plus"] - repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.3 + rev: v3.9.0 hooks: - id: reorder-python-imports name: Reorder Python imports (src, tests) @@ -16,7 +16,7 @@ repos: args: ["--application-directories", "src"] additional_dependencies: ["setuptools>60.9"] - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 22.10.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 From 9f99425aaf3433c892d0aeb941427f0766b02e2d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 20:38:32 -0800 Subject: [PATCH 006/119] [pre-commit.ci] pre-commit autoupdate (#4862) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/peterdemin/pip-compile-multi: v2.4.6 → v2.5.0](https://github.com/peterdemin/pip-compile-multi/compare/v2.4.6...v2.5.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8fbb474ec0..1781bc90f8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - flake8-bugbear - flake8-implicit-str-concat - repo: https://github.com/peterdemin/pip-compile-multi - rev: v2.4.6 + rev: v2.5.0 hooks: - id: pip-compile-multi-verify - repo: https://github.com/pre-commit/pre-commit-hooks From d178653b5f7a5ee2ba15e215ce60caeeb9ed82e1 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 25 Nov 2022 07:39:54 -0800 Subject: [PATCH 007/119] update requirements --- .pre-commit-config.yaml | 11 +++++------ requirements/dev.txt | 18 ++++++++++-------- requirements/docs.txt | 10 +++++----- requirements/tests.txt | 8 +------- requirements/typing.txt | 10 ++++------ src/flask/app.py | 4 ++-- src/flask/blueprints.py | 8 ++++---- src/flask/ctx.py | 2 +- src/flask/helpers.py | 6 +++--- src/flask/testing.py | 2 +- src/flask/wrappers.py | 2 +- tests/typing/typing_app_decorators.py | 4 ++-- 12 files changed, 39 insertions(+), 46 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1781bc90f8..b4ee36a028 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,10 +3,10 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.2 hooks: - id: pyupgrade - args: ["--py36-plus"] + args: ["--py37-plus"] - repo: https://github.com/asottile/reorder_python_imports rev: v3.9.0 hooks: @@ -14,24 +14,23 @@ repos: name: Reorder Python imports (src, tests) files: "^(?!examples/)" args: ["--application-directories", "src"] - additional_dependencies: ["setuptools>60.9"] - repo: https://github.com/psf/black rev: 22.10.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 additional_dependencies: - flake8-bugbear - flake8-implicit-str-concat - repo: https://github.com/peterdemin/pip-compile-multi - rev: v2.5.0 + rev: v2.6.1 hooks: - id: pip-compile-multi-verify - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: fix-byte-order-marker - id: trailing-whitespace diff --git a/requirements/dev.txt b/requirements/dev.txt index 4796a63359..7597d9c9c2 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,7 +8,7 @@ -r docs.txt -r tests.txt -r typing.txt -build==0.8.0 +build==0.9.0 # via pip-tools cfgv==3.3.1 # via pre-commit @@ -22,20 +22,22 @@ filelock==3.8.0 # via # tox # virtualenv -identify==2.5.5 +identify==2.5.9 # via pre-commit nodeenv==1.7.0 # via pre-commit pep517==0.13.0 # via build -pip-compile-multi==2.4.6 +pip-compile-multi==2.6.1 # via -r requirements/dev.in -pip-tools==6.8.0 +pip-tools==6.10.0 # via pip-compile-multi -platformdirs==2.5.2 +platformdirs==2.5.4 # via virtualenv pre-commit==2.20.0 # via -r requirements/dev.in +py==1.11.0 + # via tox pyyaml==6.0 # via pre-commit six==1.16.0 @@ -44,13 +46,13 @@ toml==0.10.2 # via pre-commit toposort==1.7 # via pip-compile-multi -tox==3.26.0 +tox==3.27.1 # via -r requirements/dev.in -virtualenv==20.16.5 +virtualenv==20.16.7 # via # pre-commit # tox -wheel==0.37.1 +wheel==0.38.4 # via pip-tools # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/docs.txt b/requirements/docs.txt index db4c099a5d..7c7e586987 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -7,9 +7,9 @@ # alabaster==0.7.12 # via sphinx -babel==2.10.3 +babel==2.11.0 # via sphinx -certifi==2022.6.15.1 +certifi==2022.9.24 # via requests charset-normalizer==2.1.1 # via requests @@ -17,7 +17,7 @@ docutils==0.17.1 # via # sphinx # sphinx-tabs -idna==3.3 +idna==3.4 # via requests imagesize==1.4.1 # via sphinx @@ -37,7 +37,7 @@ pygments==2.13.0 # sphinx-tabs pyparsing==3.0.9 # via packaging -pytz==2022.2.1 +pytz==2022.6 # via babel requests==2.28.1 # via sphinx @@ -68,5 +68,5 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -urllib3==1.26.12 +urllib3==1.26.13 # via requests diff --git a/requirements/tests.txt b/requirements/tests.txt index 1292951717..b6bc00fc01 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -11,21 +11,15 @@ attrs==22.1.0 # via pytest blinker==1.5 # via -r requirements/tests.in -greenlet==1.1.3 ; python_version < "3.11" - # via -r requirements/tests.in iniconfig==1.1.1 # via pytest packaging==21.3 # via pytest pluggy==1.0.0 # via pytest -py==1.11.0 - # via pytest pyparsing==3.0.9 # via packaging -pytest==7.1.3 +pytest==7.2.0 # via -r requirements/tests.in python-dotenv==0.21.0 # via -r requirements/tests.in -tomli==2.0.1 - # via pytest diff --git a/requirements/typing.txt b/requirements/typing.txt index 5c15669e45..491a09fb3c 100644 --- a/requirements/typing.txt +++ b/requirements/typing.txt @@ -7,21 +7,19 @@ # cffi==1.15.1 # via cryptography -cryptography==38.0.1 +cryptography==38.0.3 # via -r requirements/typing.in -mypy==0.971 +mypy==0.991 # via -r requirements/typing.in mypy-extensions==0.4.3 # via mypy pycparser==2.21 # via cffi -tomli==2.0.1 - # via mypy types-contextvars==2.4.7 # via -r requirements/typing.in types-dataclasses==0.6.6 # via -r requirements/typing.in -types-setuptools==65.3.0 +types-setuptools==65.6.0.1 # via -r requirements/typing.in -typing-extensions==4.3.0 +typing-extensions==4.4.0 # via mypy diff --git a/src/flask/app.py b/src/flask/app.py index db442c9edf..fed76e3058 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -384,7 +384,7 @@ def use_x_sendfile(self, value: bool) -> None: _json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None @property # type: ignore[override] - def json_encoder(self) -> t.Type[json.JSONEncoder]: # type: ignore[override] + def json_encoder(self) -> t.Type[json.JSONEncoder]: """The JSON encoder class to use. Defaults to :class:`~flask.json.JSONEncoder`. @@ -423,7 +423,7 @@ def json_encoder(self, value: t.Type[json.JSONEncoder]) -> None: self._json_encoder = value @property # type: ignore[override] - def json_decoder(self) -> t.Type[json.JSONDecoder]: # type: ignore[override] + def json_decoder(self) -> t.Type[json.JSONDecoder]: """The JSON decoder class to use. Defaults to :class:`~flask.json.JSONDecoder`. diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 104f8acf0d..c2595512db 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -176,8 +176,8 @@ class Blueprint(Scaffold): _json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None _json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None - @property # type: ignore[override] - def json_encoder( # type: ignore[override] + @property + def json_encoder( self, ) -> t.Union[t.Type[json.JSONEncoder], None]: """Blueprint-local JSON encoder class to use. Set to ``None`` to use the app's. @@ -210,8 +210,8 @@ def json_encoder(self, value: t.Union[t.Type[json.JSONEncoder], None]) -> None: ) self._json_encoder = value - @property # type: ignore[override] - def json_decoder( # type: ignore[override] + @property + def json_decoder( self, ) -> t.Union[t.Type[json.JSONDecoder], None]: """Blueprint-local JSON decoder class to use. Set to ``None`` to use the app's. diff --git a/src/flask/ctx.py b/src/flask/ctx.py index ca2844944c..c79c26dc96 100644 --- a/src/flask/ctx.py +++ b/src/flask/ctx.py @@ -307,7 +307,7 @@ def __init__( self.app = app if request is None: request = app.request_class(environ) - request.json_module = app.json # type: ignore[misc] + request.json_module = app.json self.request: Request = request self.url_adapter = None try: diff --git a/src/flask/helpers.py b/src/flask/helpers.py index 15990d0e82..3833cb8a0a 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -149,7 +149,7 @@ def generator() -> t.Generator: yield from gen finally: if hasattr(gen, "close"): - gen.close() # type: ignore + 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 @@ -287,7 +287,7 @@ def redirect( return _wz_redirect(location, code=code, Response=Response) -def abort( # type: ignore[misc] +def abort( code: t.Union[int, "BaseResponse"], *args: t.Any, **kwargs: t.Any ) -> "te.NoReturn": """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given @@ -617,7 +617,7 @@ def get_root_path(import_name: str) -> str: return os.getcwd() if hasattr(loader, "get_filename"): - filepath = loader.get_filename(import_name) # type: ignore + filepath = loader.get_filename(import_name) else: # Fall back to imports. __import__(import_name) diff --git a/src/flask/testing.py b/src/flask/testing.py index ec9ebb9def..3b21b093fb 100644 --- a/src/flask/testing.py +++ b/src/flask/testing.py @@ -225,7 +225,7 @@ def open( buffered=buffered, follow_redirects=follow_redirects, ) - response.json_module = self.application.json # type: ignore[misc] + response.json_module = self.application.json # type: ignore[assignment] # Re-push contexts that were preserved during the request. while self._new_contexts: diff --git a/src/flask/wrappers.py b/src/flask/wrappers.py index 4b855bfccc..e36a72cb47 100644 --- a/src/flask/wrappers.py +++ b/src/flask/wrappers.py @@ -25,7 +25,7 @@ class Request(RequestBase): specific ones. """ - json_module = json + 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 diff --git a/tests/typing/typing_app_decorators.py b/tests/typing/typing_app_decorators.py index 3df3e716fb..6b2188aa60 100644 --- a/tests/typing/typing_app_decorators.py +++ b/tests/typing/typing_app_decorators.py @@ -10,12 +10,12 @@ @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 From d951a763fb90ff3edb5aede5198198ee5d30d44e Mon Sep 17 00:00:00 2001 From: Abdur-Rahmaan Janhangeer Date: Sun, 27 Nov 2022 13:51:09 +0400 Subject: [PATCH 008/119] Fix backstick not rendering. --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index bdcbdcd43c..aa966e5c0d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -47,7 +47,7 @@ Debug Mode The :data:`DEBUG` config value is special because it may behave inconsistently if changed after the app has begun setting up. In order to set debug mode reliably, use the -``--debug`` option on the ``flask`` command.``flask run`` will use the interactive +``--debug`` option on the ``flask`` command. ``flask run`` will use the interactive debugger and reloader by default in debug mode. .. code-block:: text From 9a294a640141ea8ab108e5c0964d75d91a636c0a Mon Sep 17 00:00:00 2001 From: Dosenpfand Date: Sun, 27 Nov 2022 12:53:08 +0100 Subject: [PATCH 009/119] Fix class-based views example --- docs/extensiondev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 7b54917dd4..4ddb6da03b 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -212,7 +212,7 @@ class's :meth:`~views.View.as_view` method. def __init__(self, model): self.model = model - def get(id): + def get(self, id): post = self.model.query.get(id) return jsonify(post.to_json()) From 3e932aa103661e78a0946e15f35b9f4da3908d39 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 00:34:53 +0000 Subject: [PATCH 010/119] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b4ee36a028..45b3e0f2fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.0 hooks: - id: pyupgrade args: ["--py37-plus"] From 95b666871c7a89f2793952ea06d1c45e0ae5a38a Mon Sep 17 00:00:00 2001 From: rayanth Date: Tue, 13 Dec 2022 13:27:38 -0800 Subject: [PATCH 011/119] Update api.rst Corrected typo in "tojson" example, `const names = {{ names|tojson }};` was `const names = {{ names|tosjon }};` --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 2b214582e7..afbe0b79e6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -248,7 +248,7 @@ HTML `` From 43ef559de3ecb1915aabe8156f357d7ac4cd22b3 Mon Sep 17 00:00:00 2001 From: "Maxim G. Ivanov" Date: Wed, 21 Dec 2022 10:41:11 +0700 Subject: [PATCH 012/119] Fix varname in docs --- docs/views.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/views.rst b/docs/views.rst index 3eebbfaacf..8937d7b55c 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -256,7 +256,7 @@ provide get (list) and post (create) methods. return self.model.query.get_or_404(id) def get(self, id): - user = self._get_item(id) + item = self._get_item(id) return jsonify(item.to_json()) def patch(self, id): From 43bc7330cece1dbe75e7d6c326a0dea8d5652699 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 22 Dec 2022 09:23:48 -0800 Subject: [PATCH 013/119] update requirements --- .pre-commit-config.yaml | 4 ++-- requirements/dev.txt | 26 ++++++++++++++++---------- requirements/docs.txt | 10 ++++------ requirements/tests.txt | 8 +++----- requirements/typing.txt | 4 ++-- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 45b3e0f2fa..2b1dee57df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: autoupdate_schedule: monthly repos: - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 + rev: v3.3.1 hooks: - id: pyupgrade args: ["--py37-plus"] @@ -15,7 +15,7 @@ repos: files: "^(?!examples/)" args: ["--application-directories", "src"] - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 diff --git a/requirements/dev.txt b/requirements/dev.txt index 7597d9c9c2..41b2619ce3 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -10,19 +10,25 @@ -r typing.txt build==0.9.0 # via pip-tools +cachetools==5.2.0 + # via tox cfgv==3.3.1 # via pre-commit +chardet==5.1.0 + # via tox click==8.1.3 # via # pip-compile-multi # pip-tools +colorama==0.4.6 + # via tox distlib==0.3.6 # via virtualenv -filelock==3.8.0 +filelock==3.8.2 # via # tox # virtualenv -identify==2.5.9 +identify==2.5.11 # via pre-commit nodeenv==1.7.0 # via pre-commit @@ -30,25 +36,25 @@ pep517==0.13.0 # via build pip-compile-multi==2.6.1 # via -r requirements/dev.in -pip-tools==6.10.0 +pip-tools==6.12.1 # via pip-compile-multi -platformdirs==2.5.4 - # via virtualenv +platformdirs==2.6.0 + # via + # tox + # virtualenv pre-commit==2.20.0 # via -r requirements/dev.in -py==1.11.0 +pyproject-api==1.2.1 # via tox pyyaml==6.0 # via pre-commit -six==1.16.0 - # via tox toml==0.10.2 # via pre-commit toposort==1.7 # via pip-compile-multi -tox==3.27.1 +tox==4.0.16 # via -r requirements/dev.in -virtualenv==20.16.7 +virtualenv==20.17.1 # via # pre-commit # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index 7c7e586987..b1e46bde86 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -9,7 +9,7 @@ alabaster==0.7.12 # via sphinx babel==2.11.0 # via sphinx -certifi==2022.9.24 +certifi==2022.12.7 # via requests charset-normalizer==2.1.1 # via requests @@ -25,19 +25,17 @@ jinja2==3.1.2 # via sphinx markupsafe==2.1.1 # via jinja2 -packaging==21.3 +packaging==22.0 # via # pallets-sphinx-themes # sphinx -pallets-sphinx-themes==2.0.2 +pallets-sphinx-themes==2.0.3 # via -r requirements/docs.in pygments==2.13.0 # via # sphinx # sphinx-tabs -pyparsing==3.0.9 - # via packaging -pytz==2022.6 +pytz==2022.7 # via babel requests==2.28.1 # via sphinx diff --git a/requirements/tests.txt b/requirements/tests.txt index b6bc00fc01..aff42de283 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -5,20 +5,18 @@ # # pip-compile-multi # -asgiref==3.5.2 +asgiref==3.6.0 # via -r requirements/tests.in -attrs==22.1.0 +attrs==22.2.0 # via pytest blinker==1.5 # via -r requirements/tests.in iniconfig==1.1.1 # via pytest -packaging==21.3 +packaging==22.0 # via pytest pluggy==1.0.0 # via pytest -pyparsing==3.0.9 - # via packaging pytest==7.2.0 # via -r requirements/tests.in python-dotenv==0.21.0 diff --git a/requirements/typing.txt b/requirements/typing.txt index 491a09fb3c..ad8dd594df 100644 --- a/requirements/typing.txt +++ b/requirements/typing.txt @@ -7,7 +7,7 @@ # cffi==1.15.1 # via cryptography -cryptography==38.0.3 +cryptography==38.0.4 # via -r requirements/typing.in mypy==0.991 # via -r requirements/typing.in @@ -19,7 +19,7 @@ types-contextvars==2.4.7 # via -r requirements/typing.in types-dataclasses==0.6.6 # via -r requirements/typing.in -types-setuptools==65.6.0.1 +types-setuptools==65.6.0.2 # via -r requirements/typing.in typing-extensions==4.4.0 # via mypy From 8e3128b9893ba4ec8d120e1c2f479f61bd10a476 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 22 Dec 2022 09:30:31 -0800 Subject: [PATCH 014/119] ignore flake8 b905 zip(strict=True) until python 3.10 --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index e858d13a20..ea7f66e20a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -80,6 +80,8 @@ ignore = E722 # bin op line break, invalid W503 + # requires Python 3.10 + B905 # up to 88 allowed by bugbear B950 max-line-length = 80 per-file-ignores = From 1a68768e6b0aa19bd670ca050ad81adff55ba479 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Thu, 1 Dec 2022 18:57:08 +0600 Subject: [PATCH 015/119] python 2 style inheritance clean up from docs --- docs/patterns/appdispatch.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index 0c5e846efd..efa470a78e 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -93,7 +93,7 @@ exist yet, it is dynamically created and remembered:: from threading import Lock - class SubdomainDispatcher(object): + class SubdomainDispatcher: def __init__(self, domain, create_app): self.domain = domain @@ -148,7 +148,7 @@ request path up to the first slash:: from threading import Lock from werkzeug.wsgi import pop_path_info, peek_path_info - class PathDispatcher(object): + class PathDispatcher: def __init__(self, default_app, create_app): self.default_app = default_app From 6fcf6d00bd36f7c3c51115199615dc93c4a3007a Mon Sep 17 00:00:00 2001 From: "Maxim G. Ivanov" Date: Thu, 22 Dec 2022 23:23:23 +0700 Subject: [PATCH 016/119] Fix URL "committing as you go" --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d5e3a3f776..8d209048b8 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -171,7 +171,7 @@ Start coding $ git push --set-upstream fork your-branch-name -.. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes +.. _committing as you go: https://afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes .. _create a pull request: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request From 677a0468481a0f620bdb217e8f1a24ebe94681e5 Mon Sep 17 00:00:00 2001 From: "Maxim G. Ivanov" Date: Thu, 22 Dec 2022 23:23:23 +0700 Subject: [PATCH 017/119] Fix URL "committing as you go" --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d5e3a3f776..8d209048b8 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -171,7 +171,7 @@ Start coding $ git push --set-upstream fork your-branch-name -.. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes +.. _committing as you go: https://afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes .. _create a pull request: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request From 09112cfc477a270f4b2990e1daf39db1dbe30e98 Mon Sep 17 00:00:00 2001 From: "Maxim G. Ivanov" Date: Fri, 23 Dec 2022 00:19:18 +0700 Subject: [PATCH 018/119] template_folder type allows pathlib --- CHANGES.rst | 2 ++ src/flask/app.py | 2 +- src/flask/blueprints.py | 2 +- src/flask/scaffold.py | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 94257a8b3b..fa1a626908 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,8 @@ Version 2.2.3 Unreleased +- Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892` + Version 2.2.2 ------------- diff --git a/src/flask/app.py b/src/flask/app.py index fed76e3058..5315b71cb5 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -558,7 +558,7 @@ def __init__( static_host: t.Optional[str] = None, host_matching: bool = False, subdomain_matching: bool = False, - template_folder: t.Optional[str] = "templates", + template_folder: t.Optional[t.Union[str, os.PathLike]] = "templates", instance_path: t.Optional[str] = None, instance_relative_config: bool = False, root_path: t.Optional[str] = None, diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index c2595512db..f6d62ba83f 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -250,7 +250,7 @@ def __init__( import_name: str, static_folder: t.Optional[t.Union[str, os.PathLike]] = None, static_url_path: t.Optional[str] = None, - template_folder: t.Optional[str] = None, + template_folder: t.Optional[t.Union[str, os.PathLike]] = None, url_prefix: t.Optional[str] = None, subdomain: t.Optional[str] = None, url_defaults: t.Optional[dict] = None, diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index 1530a11ec8..ebfc741f1a 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -93,7 +93,7 @@ def __init__( import_name: str, static_folder: t.Optional[t.Union[str, os.PathLike]] = None, static_url_path: t.Optional[str] = None, - template_folder: t.Optional[str] = None, + template_folder: t.Optional[t.Union[str, os.PathLike]] = None, root_path: t.Optional[str] = None, ): #: The name of the package or module that this object belongs From 79032ca5f1c4747e5aeaa193bdeb2e4eae410ea6 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 30 Oct 2022 07:55:51 -0700 Subject: [PATCH 019/119] Add .svg to select_jinja_autoescape (#4840) As SVG files are a type of XML file and are similar in nearly all aspects to XML, .svg should also be autoescaped. --- CHANGES.rst | 3 +++ docs/templating.rst | 2 +- src/flask/app.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fa1a626908..82d2da6d8d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,9 @@ Version 2.2.3 Unreleased +- Autoescaping is now enabled by default for ``.svg`` files. Inside + templates this behavior can be changed with the ``autoescape`` tag. + :issue:`4831` - Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892` diff --git a/docs/templating.rst b/docs/templating.rst index 3cda995e44..f497de7333 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -18,7 +18,7 @@ Jinja Setup Unless customized, Jinja2 is configured by Flask as follows: - autoescaping is enabled for all templates ending in ``.html``, - ``.htm``, ``.xml`` as well as ``.xhtml`` when using + ``.htm``, ``.xml``, ``.xhtml``, as well as ``.svg`` when using :func:`~flask.templating.render_template`. - autoescaping is enabled for all strings when using :func:`~flask.templating.render_template_string`. diff --git a/src/flask/app.py b/src/flask/app.py index 5315b71cb5..0ac4bbb5ae 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -961,11 +961,14 @@ 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")) + return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg")) def update_template_context(self, context: dict) -> None: """Update the template context with some commonly used variables. From 4bc0e4943dfa637361aec2bb18dc9e1fabeaad12 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Sun, 21 Aug 2022 17:36:30 +0800 Subject: [PATCH 020/119] Add --debug option to flask run --- CHANGES.rst | 1 + src/flask/cli.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 82d2da6d8d..79e66e956d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ Unreleased templates this behavior can be changed with the ``autoescape`` tag. :issue:`4831` - Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892` +- Add ``--debug`` option to the ``flask run`` command. :issue:`4777` Version 2.2.2 diff --git a/src/flask/cli.py b/src/flask/cli.py index 82fe8194eb..10e9c1e99c 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -837,6 +837,11 @@ def convert(self, value, param, ctx): expose_value=False, help="The key file to use when specifying a certificate.", ) +@click.option( + "--debug/--no-debug", + default=None, + help="Enable or disable the debug mode.", +) @click.option( "--reload/--no-reload", default=None, @@ -878,6 +883,7 @@ def run_command( info, host, port, + debug, reload, debugger, with_threads, @@ -910,7 +916,8 @@ def app(environ, start_response): # command fails. raise e from None - debug = get_debug_flag() + if debug is None: + debug = get_debug_flag() if reload is None: reload = debug From bd26928fdb2476fca62f0e621e8f2870250ac2bc Mon Sep 17 00:00:00 2001 From: Grey Li Date: Tue, 23 Aug 2022 12:44:50 +0800 Subject: [PATCH 021/119] Prefer flask run --debug in docs --- docs/_static/pycharm-run-config.png | Bin 77462 -> 99654 bytes docs/cli.rst | 4 ++-- docs/config.rst | 6 +++--- docs/debugging.rst | 4 ++-- docs/quickstart.rst | 2 +- docs/server.rst | 2 +- docs/tutorial/factory.rst | 2 +- examples/tutorial/README.rst | 2 +- src/flask/cli.py | 12 ++++-------- 9 files changed, 15 insertions(+), 19 deletions(-) diff --git a/docs/_static/pycharm-run-config.png b/docs/_static/pycharm-run-config.png index 3f78915796e34c54a90008782caa61df47ea248a..ad025545a7ff00c6366a6146d808d875c5eb6a0a 100644 GIT binary patch literal 99654 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 literal 77462 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

YIiEQ5^T7%brmC|dy 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 diff --git a/docs/cli.rst b/docs/cli.rst index 22484f1731..a3ea268eb3 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -95,7 +95,7 @@ the ``--debug`` option. .. code-block:: console - $ flask --app hello --debug run + $ flask --app hello run --debug * Serving Flask app "hello" * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) @@ -550,7 +550,7 @@ a name such as "flask run". Click the *Script path* dropdown and change it to *Module name*, then input ``flask``. The *Parameters* field is set to the CLI command to execute along with any arguments. -This example uses ``--app hello --debug run``, which will run the development server in +This example uses ``--app hello run --debug``, which will run the development server in debug mode. ``--app hello`` should be the import or file with your Flask app. If you installed your project as a package in your virtualenv, you may uncheck the diff --git a/docs/config.rst b/docs/config.rst index aa966e5c0d..9446e4565f 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -47,15 +47,15 @@ Debug Mode The :data:`DEBUG` config value is special because it may behave inconsistently if changed after the app has begun setting up. In order to set debug mode reliably, use the -``--debug`` option on the ``flask`` command. ``flask run`` will use the interactive +``--debug`` option on the ``flask run`` command. ``flask run`` will use the interactive debugger and reloader by default in debug mode. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug Using the option is recommended. While it is possible to set :data:`DEBUG` in your -config or code, this is strongly discouraged. It can't be read early by the ``flask`` +config or code, this is strongly discouraged. It can't be read early by the ``flask run`` command, and some systems or extensions may have already configured themselves based on a previous value. diff --git a/docs/debugging.rst b/docs/debugging.rst index fb3604b036..18f4286758 100644 --- a/docs/debugging.rst +++ b/docs/debugging.rst @@ -43,7 +43,7 @@ The debugger is enabled by default when the development server is run in debug m .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug When running from Python code, passing ``debug=True`` enables debug mode, which is mostly equivalent. @@ -72,7 +72,7 @@ which can interfere. .. code-block:: text - $ flask --app hello --debug run --no-debugger --no-reload + $ flask --app hello run --debug --no-debugger --no-reload When running from Python: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 02dbc97835..ad9e3bc4e8 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -108,7 +108,7 @@ To enable debug mode, use the ``--debug`` option. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug * Serving Flask app 'hello' * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) diff --git a/docs/server.rst b/docs/server.rst index a34dfab5dd..d38aa12089 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -24,7 +24,7 @@ debug mode. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug This enables debug mode, including the interactive debugger and reloader, and then starts the server on http://localhost:5000/. Use ``flask run --help`` to see the diff --git a/docs/tutorial/factory.rst b/docs/tutorial/factory.rst index c8e2c5f4e0..39febd135f 100644 --- a/docs/tutorial/factory.rst +++ b/docs/tutorial/factory.rst @@ -137,7 +137,7 @@ follow the tutorial. .. code-block:: text - $ flask --app flaskr --debug run + $ flask --app flaskr run --debug You'll see output similar to this: diff --git a/examples/tutorial/README.rst b/examples/tutorial/README.rst index a7e12ca250..1c745078bc 100644 --- a/examples/tutorial/README.rst +++ b/examples/tutorial/README.rst @@ -48,7 +48,7 @@ Run .. code-block:: text $ flask --app flaskr init-db - $ flask --app flaskr --debug run + $ flask --app flaskr run --debug Open http://127.0.0.1:5000 in a browser. diff --git a/src/flask/cli.py b/src/flask/cli.py index 10e9c1e99c..37a15ff2d8 100644 --- a/src/flask/cli.py +++ b/src/flask/cli.py @@ -837,11 +837,6 @@ def convert(self, value, param, ctx): expose_value=False, help="The key file to use when specifying a certificate.", ) -@click.option( - "--debug/--no-debug", - default=None, - help="Enable or disable the debug mode.", -) @click.option( "--reload/--no-reload", default=None, @@ -883,7 +878,6 @@ def run_command( info, host, port, - debug, reload, debugger, with_threads, @@ -916,8 +910,7 @@ def app(environ, start_response): # command fails. raise e from None - if debug is None: - debug = get_debug_flag() + debug = get_debug_flag() if reload is None: reload = debug @@ -940,6 +933,9 @@ def app(environ, start_response): ) +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: From 4d69165ab6e17fa754139d348cdfd9edacbcb999 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 29 Dec 2022 09:51:34 -0800 Subject: [PATCH 022/119] revert run debug docs --- docs/cli.rst | 4 ++-- docs/config.rst | 4 ++-- docs/debugging.rst | 4 ++-- docs/quickstart.rst | 2 +- docs/server.rst | 2 +- docs/tutorial/factory.rst | 2 +- examples/tutorial/README.rst | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index a3ea268eb3..22484f1731 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -95,7 +95,7 @@ the ``--debug`` option. .. code-block:: console - $ flask --app hello run --debug + $ flask --app hello --debug run * Serving Flask app "hello" * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) @@ -550,7 +550,7 @@ a name such as "flask run". Click the *Script path* dropdown and change it to *Module name*, then input ``flask``. The *Parameters* field is set to the CLI command to execute along with any arguments. -This example uses ``--app hello run --debug``, which will run the development server in +This example uses ``--app hello --debug run``, which will run the development server in debug mode. ``--app hello`` should be the import or file with your Flask app. If you installed your project as a package in your virtualenv, you may uncheck the diff --git a/docs/config.rst b/docs/config.rst index 9446e4565f..7cffc44bb2 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -47,12 +47,12 @@ Debug Mode The :data:`DEBUG` config value is special because it may behave inconsistently if changed after the app has begun setting up. In order to set debug mode reliably, use the -``--debug`` option on the ``flask run`` command. ``flask run`` will use the interactive +``--debug`` option on the ``flask`` command. ``flask run`` will use the interactive debugger and reloader by default in debug mode. .. code-block:: text - $ flask --app hello run --debug + $ flask --app hello --debug run Using the option is recommended. While it is possible to set :data:`DEBUG` in your config or code, this is strongly discouraged. It can't be read early by the ``flask run`` diff --git a/docs/debugging.rst b/docs/debugging.rst index 18f4286758..fb3604b036 100644 --- a/docs/debugging.rst +++ b/docs/debugging.rst @@ -43,7 +43,7 @@ The debugger is enabled by default when the development server is run in debug m .. code-block:: text - $ flask --app hello run --debug + $ flask --app hello --debug run When running from Python code, passing ``debug=True`` enables debug mode, which is mostly equivalent. @@ -72,7 +72,7 @@ which can interfere. .. code-block:: text - $ flask --app hello run --debug --no-debugger --no-reload + $ flask --app hello --debug run --no-debugger --no-reload When running from Python: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index ad9e3bc4e8..02dbc97835 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -108,7 +108,7 @@ To enable debug mode, use the ``--debug`` option. .. code-block:: text - $ flask --app hello run --debug + $ flask --app hello --debug run * Serving Flask app 'hello' * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) diff --git a/docs/server.rst b/docs/server.rst index d38aa12089..a34dfab5dd 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -24,7 +24,7 @@ debug mode. .. code-block:: text - $ flask --app hello run --debug + $ flask --app hello --debug run This enables debug mode, including the interactive debugger and reloader, and then starts the server on http://localhost:5000/. Use ``flask run --help`` to see the diff --git a/docs/tutorial/factory.rst b/docs/tutorial/factory.rst index 39febd135f..c8e2c5f4e0 100644 --- a/docs/tutorial/factory.rst +++ b/docs/tutorial/factory.rst @@ -137,7 +137,7 @@ follow the tutorial. .. code-block:: text - $ flask --app flaskr run --debug + $ flask --app flaskr --debug run You'll see output similar to this: diff --git a/examples/tutorial/README.rst b/examples/tutorial/README.rst index 1c745078bc..a7e12ca250 100644 --- a/examples/tutorial/README.rst +++ b/examples/tutorial/README.rst @@ -48,7 +48,7 @@ Run .. code-block:: text $ flask --app flaskr init-db - $ flask --app flaskr run --debug + $ flask --app flaskr --debug run Open http://127.0.0.1:5000 in a browser. From 74e5263c88e51bb442879ab863a5d03292fc0a33 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 29 Dec 2022 09:52:18 -0800 Subject: [PATCH 023/119] new run debug docs --- docs/cli.rst | 4 ++-- docs/config.rst | 4 ++-- docs/debugging.rst | 4 ++-- docs/quickstart.rst | 2 +- docs/server.rst | 2 +- docs/tutorial/factory.rst | 2 +- examples/tutorial/README.rst | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 22484f1731..a3ea268eb3 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -95,7 +95,7 @@ the ``--debug`` option. .. code-block:: console - $ flask --app hello --debug run + $ flask --app hello run --debug * Serving Flask app "hello" * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) @@ -550,7 +550,7 @@ a name such as "flask run". Click the *Script path* dropdown and change it to *Module name*, then input ``flask``. The *Parameters* field is set to the CLI command to execute along with any arguments. -This example uses ``--app hello --debug run``, which will run the development server in +This example uses ``--app hello run --debug``, which will run the development server in debug mode. ``--app hello`` should be the import or file with your Flask app. If you installed your project as a package in your virtualenv, you may uncheck the diff --git a/docs/config.rst b/docs/config.rst index 7cffc44bb2..9446e4565f 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -47,12 +47,12 @@ Debug Mode The :data:`DEBUG` config value is special because it may behave inconsistently if changed after the app has begun setting up. In order to set debug mode reliably, use the -``--debug`` option on the ``flask`` command. ``flask run`` will use the interactive +``--debug`` option on the ``flask run`` command. ``flask run`` will use the interactive debugger and reloader by default in debug mode. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug Using the option is recommended. While it is possible to set :data:`DEBUG` in your config or code, this is strongly discouraged. It can't be read early by the ``flask run`` diff --git a/docs/debugging.rst b/docs/debugging.rst index fb3604b036..18f4286758 100644 --- a/docs/debugging.rst +++ b/docs/debugging.rst @@ -43,7 +43,7 @@ The debugger is enabled by default when the development server is run in debug m .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug When running from Python code, passing ``debug=True`` enables debug mode, which is mostly equivalent. @@ -72,7 +72,7 @@ which can interfere. .. code-block:: text - $ flask --app hello --debug run --no-debugger --no-reload + $ flask --app hello run --debug --no-debugger --no-reload When running from Python: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index f92bd24100..0d7ad3f695 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -108,7 +108,7 @@ To enable debug mode, use the ``--debug`` option. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug * Serving Flask app 'hello' * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) diff --git a/docs/server.rst b/docs/server.rst index a34dfab5dd..d38aa12089 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -24,7 +24,7 @@ debug mode. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug This enables debug mode, including the interactive debugger and reloader, and then starts the server on http://localhost:5000/. Use ``flask run --help`` to see the diff --git a/docs/tutorial/factory.rst b/docs/tutorial/factory.rst index c8e2c5f4e0..39febd135f 100644 --- a/docs/tutorial/factory.rst +++ b/docs/tutorial/factory.rst @@ -137,7 +137,7 @@ follow the tutorial. .. code-block:: text - $ flask --app flaskr --debug run + $ flask --app flaskr run --debug You'll see output similar to this: diff --git a/examples/tutorial/README.rst b/examples/tutorial/README.rst index a7e12ca250..1c745078bc 100644 --- a/examples/tutorial/README.rst +++ b/examples/tutorial/README.rst @@ -48,7 +48,7 @@ Run .. code-block:: text $ flask --app flaskr init-db - $ flask --app flaskr --debug run + $ flask --app flaskr run --debug Open http://127.0.0.1:5000 in a browser. From bb1f83c26586f1aa254d67a67e6b692034b4cb4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Jan 2023 16:01:00 +0000 Subject: [PATCH 024/119] Bump dessant/lock-threads from 3 to 4 Bumps [dessant/lock-threads](https://github.com/dessant/lock-threads) from 3 to 4. - [Release notes](https://github.com/dessant/lock-threads/releases) - [Changelog](https://github.com/dessant/lock-threads/blob/master/CHANGELOG.md) - [Commits](https://github.com/dessant/lock-threads/compare/v3...v4) --- updated-dependencies: - dependency-name: dessant/lock-threads dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml index cd89f67c9d..a84a3a7370 100644 --- a/.github/workflows/lock.yaml +++ b/.github/workflows/lock.yaml @@ -11,7 +11,7 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v3 + - uses: dessant/lock-threads@v4 with: github-token: ${{ github.token }} issue-inactive-days: 14 From d7b6c1f6703df405c69da45e7e0ba3d1aed512ce Mon Sep 17 00:00:00 2001 From: Josh Michael Karamuth Date: Mon, 31 Oct 2022 12:49:16 +0400 Subject: [PATCH 025/119] Fix subdomain inheritance for nested blueprints. Fixes #4834 --- src/flask/blueprints.py | 9 ++++++++ tests/test_blueprints.py | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index f6d62ba83f..1278fb8ce5 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -453,6 +453,15 @@ def extend(bp_dict, parent_dict): 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 None: + bp_options["subdomain"] = state.subdomain + elif bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain if bp_url_prefix is None: bp_url_prefix = blueprint.url_prefix diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 1bf130f52b..a83ae243e7 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -950,6 +950,53 @@ def index(): 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_overrides_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}.{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__) From cabda5935322d75e7aedb3ee6d59fb7ab62bd674 Mon Sep 17 00:00:00 2001 From: pgjones Date: Wed, 4 Jan 2023 16:45:20 +0000 Subject: [PATCH 026/119] Ensure that blueprint subdomains suffix-chain This ensures that a child's subdomain prefixs any parent subdomain such that the full domain is child.parent.domain.tld and onwards with further nesting. This makes the most sense to users and mimics how url_prefixes work (although subdomains suffix). --- CHANGES.rst | 2 ++ docs/blueprints.rst | 13 +++++++++++++ src/flask/blueprints.py | 9 +++++++-- tests/test_blueprints.py | 6 ++++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index adf2623d59..bcec74a76b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,8 @@ Version 2.3.0 Unreleased +- Ensure subdomains are applied with nested blueprints. :issue:`4834` + Version 2.2.3 ------------- diff --git a/docs/blueprints.rst b/docs/blueprints.rst index af368bacf1..d5cf3d8237 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -140,6 +140,19 @@ name, and child URLs will be prefixed with the parent's URL prefix. url_for('parent.child.create') /parent/child/create +In addition a child blueprint's will gain their parent's subdomain, +with their subdomain as prefix if present i.e. + +.. code-block:: python + + parent = Blueprint('parent', __name__, subdomain='parent') + child = Blueprint('child', __name__, subdomain='child') + parent.register_blueprint(child) + app.register_blueprint(parent) + + url_for('parent.child.create', _external=True) + "child.parent.domain.tld" + Blueprint-specific before request functions, etc. registered with the parent will trigger for the child. If a child does not have an error handler that can handle a given exception, the parent's will be tried. diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index 1278fb8ce5..2403be1cf8 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -358,6 +358,9 @@ def register(self, app: "Flask", options: dict) -> None: :param options: Keyword arguments forwarded from :meth:`~Flask.register_blueprint`. + .. versionchanged:: 2.3 + Nested blueprints now correctly apply subdomains. + .. versionchanged:: 2.0.1 Nested blueprints are registered with their dotted name. This allows different blueprints with the same name to be @@ -458,10 +461,12 @@ def extend(bp_dict, parent_dict): if bp_subdomain is None: bp_subdomain = blueprint.subdomain - if state.subdomain is not None and bp_subdomain is None: - bp_options["subdomain"] = state.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 diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index a83ae243e7..dbe00b974c 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -971,7 +971,7 @@ def index(): assert response.status_code == 200 -def test_child_overrides_parent_subdomain(app, client) -> None: +def test_child_and_parent_subdomain(app, client) -> None: child_subdomain = "api" parent_subdomain = "parent" parent = flask.Blueprint("parent", __name__) @@ -988,7 +988,9 @@ def index(): domain_name = "domain.tld" app.config["SERVER_NAME"] = domain_name - response = client.get("/", base_url=f"http://{child_subdomain}.{domain_name}") + response = client.get( + "/", base_url=f"http://{child_subdomain}.{parent_subdomain}.{domain_name}" + ) assert response.status_code == 200 From 2a9d16d01189249749324141f0c294b31bf7ba6b Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 9 Jan 2023 10:37:04 -0800 Subject: [PATCH 027/119] update tested python versions test 3.11 final test 3.12 dev update for tox 4 --- .github/workflows/tests.yaml | 23 +++++++++++++++-------- requirements/tests-pallets-dev.in | 5 ----- requirements/tests-pallets-dev.txt | 20 -------------------- tox.ini | 20 +++++++++++++++----- 4 files changed, 30 insertions(+), 38 deletions(-) delete mode 100644 requirements/tests-pallets-dev.in delete mode 100644 requirements/tests-pallets-dev.txt diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 674fb8b73b..89e548dbad 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -24,16 +24,17 @@ jobs: fail-fast: false matrix: include: - - {name: Linux, python: '3.10', os: ubuntu-latest, tox: py310} - - {name: Windows, python: '3.10', os: windows-latest, tox: py310} - - {name: Mac, python: '3.10', os: macos-latest, tox: py310} - - {name: '3.11-dev', python: '3.11-dev', os: ubuntu-latest, tox: py311} + - {name: Linux, python: '3.11', os: ubuntu-latest, tox: py311} + - {name: Windows, python: '3.11', os: windows-latest, tox: py311} + - {name: Mac, python: '3.11', os: macos-latest, tox: py311} + - {name: '3.12-dev', python: '3.12-dev', os: ubuntu-latest, tox: py312} + - {name: '3.10', python: '3.10', os: ubuntu-latest, tox: py310} - {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39} - {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38} - {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37} - - {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37} - - {name: 'Pallets Minimum Versions', python: '3.10', os: ubuntu-latest, tox: py-min} - - {name: 'Pallets Development Versions', python: '3.7', os: ubuntu-latest, tox: py-dev} + - {name: 'PyPy', python: 'pypy-3.9', os: ubuntu-latest, tox: pypy39} + - {name: 'Pallets Minimum Versions', python: '3.11', os: ubuntu-latest, tox: py311-min} + - {name: 'Pallets Development Versions', python: '3.7', os: ubuntu-latest, tox: py37-dev} - {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing} steps: - uses: actions/checkout@v3 @@ -47,5 +48,11 @@ jobs: pip install -U wheel pip install -U setuptools python -m pip install -U pip + - name: cache mypy + uses: actions/cache@v3.2.2 + with: + path: ./.mypy_cache + key: mypy|${{ matrix.python }}|${{ hashFiles('setup.cfg') }} + if: matrix.tox == 'typing' - run: pip install tox - - run: tox -e ${{ matrix.tox }} + - run: tox run -e ${{ matrix.tox }} diff --git a/requirements/tests-pallets-dev.in b/requirements/tests-pallets-dev.in deleted file mode 100644 index dddbe48a41..0000000000 --- a/requirements/tests-pallets-dev.in +++ /dev/null @@ -1,5 +0,0 @@ -https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz -https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz -https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz -https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz -https://github.com/pallets/click/archive/refs/heads/main.tar.gz diff --git a/requirements/tests-pallets-dev.txt b/requirements/tests-pallets-dev.txt deleted file mode 100644 index a74f556b17..0000000000 --- a/requirements/tests-pallets-dev.txt +++ /dev/null @@ -1,20 +0,0 @@ -# SHA1:692b640e7f835e536628f76de0afff1296524122 -# -# This file is autogenerated by pip-compile-multi -# To update, run: -# -# pip-compile-multi -# -click @ https://github.com/pallets/click/archive/refs/heads/main.tar.gz - # via -r requirements/tests-pallets-dev.in -itsdangerous @ https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz - # via -r requirements/tests-pallets-dev.in -jinja2 @ https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz - # via -r requirements/tests-pallets-dev.in -markupsafe @ https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz - # via - # -r requirements/tests-pallets-dev.in - # jinja2 - # werkzeug -werkzeug @ https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz - # via -r requirements/tests-pallets-dev.in diff --git a/tox.ini b/tox.ini index ee4d40f689..08c6dca2f5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,8 @@ [tox] envlist = - py3{11,10,9,8,7},pypy3{8,7} - py310-min + py3{12,11,10,9,8,7} + pypy3{9,8,7} + py311-min py37-dev style typing @@ -9,12 +10,17 @@ envlist = skip_missing_interpreters = true [testenv] +package = wheel +wheel_build_env = .pkg envtmpdir = {toxworkdir}/tmp/{envname} deps = -r requirements/tests.txt min: -r requirements/tests-pallets-min.txt - dev: -r requirements/tests-pallets-dev.txt - + dev: https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz + dev: https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz + dev: https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz + dev: https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz + dev: https://github.com/pallets/click/archive/refs/heads/main.tar.gz # examples/tutorial[test] # examples/javascript[test] # commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs:tests examples} @@ -23,12 +29,16 @@ commands = pytest -v --tb=short --basetemp={envtmpdir} {posargs:tests} [testenv:style] deps = pre-commit skip_install = true -commands = pre-commit run --all-files --show-diff-on-failure +commands = pre-commit run --all-files [testenv:typing] +package = wheel +wheel_build_env = .pkg deps = -r requirements/typing.txt commands = mypy [testenv:docs] +package = wheel +wheel_build_env = .pkg deps = -r requirements/docs.txt commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html From a7487701999fee55bb76a393e72c96ac8e277fcf Mon Sep 17 00:00:00 2001 From: Bhushan Mohanraj <50306448+bhushan-mohanraj@users.noreply.github.com> Date: Fri, 6 Jan 2023 22:14:03 -0500 Subject: [PATCH 028/119] clarify `View.as_view` docstring --- src/flask/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/flask/views.py b/src/flask/views.py index a82f191238..f86172b43c 100644 --- a/src/flask/views.py +++ b/src/flask/views.py @@ -92,8 +92,8 @@ def as_view( :attr:`init_every_request` to ``False``, the same instance will be used for every request. - The arguments passed to this method are forwarded to the view - class ``__init__`` method. + 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. From 9da947a279d5ed5958609e0b6ec690160c496480 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 9 Jan 2023 12:45:16 -0800 Subject: [PATCH 029/119] set workflow permissions --- .github/workflows/lock.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml index a84a3a7370..20bec85a7c 100644 --- a/.github/workflows/lock.yaml +++ b/.github/workflows/lock.yaml @@ -1,18 +1,25 @@ -# This does not automatically close "stale" issues. Instead, it locks closed issues after 2 weeks of no activity. -# If there's a new issue related to an old one, we've found it's much easier to work on as a new issue. - name: 'Lock threads' +# Lock closed issues that have not received any further activity for +# two weeks. This does not close open issues, only humans may do that. +# We find that it is easier to respond to new issues with fresh examples +# rather than continuing discussions on old issues. on: schedule: - cron: '0 0 * * *' +permissions: + issues: write + pull-requests: write + +concurrency: + group: lock + jobs: lock: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v4 with: - github-token: ${{ github.token }} issue-inactive-days: 14 pr-inactive-days: 14 From 6d6d986fc502c2c24fe3db0ec0dbb062d053e068 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 18 Jan 2023 09:50:41 -0800 Subject: [PATCH 030/119] switch to pyproject.toml --- .flake8 | 26 ++++++++ .github/workflows/tests.yaml | 2 +- CHANGES.rst | 2 + pyproject.toml | 94 ++++++++++++++++++++++++++ setup.cfg | 123 ----------------------------------- setup.py | 17 ----- 6 files changed, 123 insertions(+), 141 deletions(-) create mode 100644 .flake8 create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..0980961647 --- /dev/null +++ b/.flake8 @@ -0,0 +1,26 @@ +[flake8] +# B = bugbear +# E = pycodestyle errors +# F = flake8 pyflakes +# W = pycodestyle warnings +# B9 = bugbear opinions +# ISC = implicit str concat +select = B, E, F, W, B9, ISC +ignore = + # slice notation whitespace, invalid + E203 + # import at top, too many circular import fixes + E402 + # line length, handled by bugbear B950 + E501 + # bare except, handled by bugbear B001 + E722 + # bin op line break, invalid + W503 + # requires Python 3.10 + B905 +# up to 88 allowed by bugbear B950 +max-line-length = 80 +per-file-ignores = + # __init__ exports names + src/flask/__init__.py: F401 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 89e548dbad..a00c535508 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -52,7 +52,7 @@ jobs: uses: actions/cache@v3.2.2 with: path: ./.mypy_cache - key: mypy|${{ matrix.python }}|${{ hashFiles('setup.cfg') }} + key: mypy|${{ matrix.python }}|${{ hashFiles('pyproject.toml') }} if: matrix.tox == 'typing' - run: pip install tox - run: tox run -e ${{ matrix.tox }} diff --git a/CHANGES.rst b/CHANGES.rst index bcec74a76b..94c16a3486 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,8 @@ Version 2.3.0 Unreleased +- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. + :pr:`4947` - Ensure subdomains are applied with nested blueprints. :issue:`4834` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..95a6e10095 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,94 @@ +[project] +name = "Flask" +description = "A simple framework for building complex web applications." +readme = "README.rst" +license = {text = "BSD-3-Clause"} +maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] +authors = [{name = "Armin Ronacher", email = "armin.ronacher@active-4.com"}] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Flask", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Internet :: WWW/HTTP :: WSGI", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + "Topic :: Software Development :: Libraries :: Application Frameworks", +] +requires-python = ">=3.7" +dependencies = [ + "Werkzeug>=2.2.2", + "Jinja2>=3.0", + "itsdangerous>=2.0", + "click>=8.0", + "importlib-metadata>=3.6.0; python_version < '3.10'", +] +dynamic = ["version"] + +[project.urls] +Donate = "https://palletsprojects.com/donate" +Documentation = "https://flask.palletsprojects.com/" +Changes = "https://flask.palletsprojects.com/changes/" +"Source Code" = "https://github.com/pallets/flask/" +"Issue Tracker" = "https://github.com/pallets/flask/issues/" +Twitter = "https://twitter.com/PalletsTeam" +Chat = "https://discord.gg/pallets" + +[project.optional-dependencies] +async = ["asgiref>=3.2"] +dotenv = ["python-dotenv"] + +[project.scripts] +flask = "flask.cli:main" + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +version = {attr = "flask.__version__"} + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = ["error"] + +[tool.coverage.run] +branch = true +source = ["flask", "tests"] + +[tool.coverage.paths] +source = ["src", "*/site-packages"] + +[tool.mypy] +python_version = "3.7" +files = ["src/flask"] +show_error_codes = true +pretty = true +#strict = true +allow_redefinition = true +disallow_subclassing_any = true +#disallow_untyped_calls = true +#disallow_untyped_defs = true +#disallow_incomplete_defs = true +no_implicit_optional = true +local_partial_types = true +#no_implicit_reexport = true +strict_equality = true +warn_redundant_casts = true +warn_unused_configs = true +warn_unused_ignores = true +#warn_return_any = true +#warn_unreachable = true + +[[tool.mypy.overrides]] +module = [ + "asgiref.*", + "blinker.*", + "dotenv.*", + "cryptography.*", + "importlib_metadata", +] +ignore_missing_imports = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ea7f66e20a..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,123 +0,0 @@ -[metadata] -name = Flask -version = attr: flask.__version__ -url = https://palletsprojects.com/p/flask -project_urls = - Donate = https://palletsprojects.com/donate - Documentation = https://flask.palletsprojects.com/ - Changes = https://flask.palletsprojects.com/changes/ - Source Code = https://github.com/pallets/flask/ - Issue Tracker = https://github.com/pallets/flask/issues/ - Twitter = https://twitter.com/PalletsTeam - Chat = https://discord.gg/pallets -license = BSD-3-Clause -author = Armin Ronacher -author_email = armin.ronacher@active-4.com -maintainer = Pallets -maintainer_email = contact@palletsprojects.com -description = A simple framework for building complex web applications. -long_description = file: README.rst -long_description_content_type = text/x-rst -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Framework :: Flask - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python - Topic :: Internet :: WWW/HTTP :: Dynamic Content - Topic :: Internet :: WWW/HTTP :: WSGI - Topic :: Internet :: WWW/HTTP :: WSGI :: Application - Topic :: Software Development :: Libraries :: Application Frameworks - -[options] -packages = find: -package_dir = = src -include_package_data = True -python_requires = >= 3.7 -# Dependencies are in setup.py for GitHub's dependency graph. - -[options.packages.find] -where = src - -[options.entry_points] -console_scripts = - flask = flask.cli:main - -[tool:pytest] -testpaths = tests -filterwarnings = - error - -[coverage:run] -branch = True -source = - flask - tests - -[coverage:paths] -source = - src - */site-packages - -[flake8] -# B = bugbear -# E = pycodestyle errors -# F = flake8 pyflakes -# W = pycodestyle warnings -# B9 = bugbear opinions -# ISC = implicit str concat -select = B, E, F, W, B9, ISC -ignore = - # slice notation whitespace, invalid - E203 - # import at top, too many circular import fixes - E402 - # line length, handled by bugbear B950 - E501 - # bare except, handled by bugbear B001 - E722 - # bin op line break, invalid - W503 - # requires Python 3.10 - B905 -# up to 88 allowed by bugbear B950 -max-line-length = 80 -per-file-ignores = - # __init__ exports names - src/flask/__init__.py: F401 - -[mypy] -files = src/flask, tests/typing -python_version = 3.7 -show_error_codes = True -allow_redefinition = True -disallow_subclassing_any = True -# disallow_untyped_calls = True -# disallow_untyped_defs = True -# disallow_incomplete_defs = True -no_implicit_optional = True -local_partial_types = True -# no_implicit_reexport = True -strict_equality = True -warn_redundant_casts = True -warn_unused_configs = True -warn_unused_ignores = True -# warn_return_any = True -# warn_unreachable = True - -[mypy-asgiref.*] -ignore_missing_imports = True - -[mypy-blinker.*] -ignore_missing_imports = True - -[mypy-dotenv.*] -ignore_missing_imports = True - -[mypy-cryptography.*] -ignore_missing_imports = True - -[mypy-importlib_metadata] -ignore_missing_imports = True diff --git a/setup.py b/setup.py deleted file mode 100644 index 6717546774..0000000000 --- a/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup - -# Metadata goes in setup.cfg. These are here for GitHub's dependency graph. -setup( - name="Flask", - install_requires=[ - "Werkzeug >= 2.2.2", - "Jinja2 >= 3.0", - "itsdangerous >= 2.0", - "click >= 8.0", - "importlib-metadata >= 3.6.0; python_version < '3.10'", - ], - extras_require={ - "async": ["asgiref >= 3.2"], - "dotenv": ["python-dotenv"], - }, -) From 8f13f5b6d672f1ba434f9c8ebe2c1b1dd385962e Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 18 Jan 2023 10:21:37 -0800 Subject: [PATCH 031/119] update docs and examples for pyproject setup.py -> pyproject.toml venv -> .venv --- CONTRIBUTING.rst | 6 ++-- docs/cli.rst | 41 +++++++--------------- docs/deploying/eventlet.rst | 4 +-- docs/deploying/gevent.rst | 4 +-- docs/deploying/gunicorn.rst | 4 +-- docs/deploying/mod_wsgi.rst | 6 ++-- docs/deploying/uwsgi.rst | 4 +-- docs/deploying/waitress.rst | 4 +-- docs/installation.rst | 10 +++--- docs/patterns/packages.rst | 25 ++++++------- docs/tutorial/deploy.rst | 21 ++++------- docs/tutorial/install.rst | 56 +++++++++++++----------------- docs/tutorial/layout.rst | 8 ++--- docs/tutorial/tests.rst | 22 ++++++------ examples/javascript/.gitignore | 2 +- examples/javascript/README.rst | 4 +-- examples/javascript/pyproject.toml | 26 ++++++++++++++ examples/javascript/setup.cfg | 29 ---------------- examples/javascript/setup.py | 3 -- examples/tutorial/.gitignore | 2 +- examples/tutorial/README.rst | 8 ++--- examples/tutorial/pyproject.toml | 28 +++++++++++++++ examples/tutorial/setup.cfg | 28 --------------- examples/tutorial/setup.py | 3 -- 24 files changed, 153 insertions(+), 195 deletions(-) create mode 100644 examples/javascript/pyproject.toml delete mode 100644 examples/javascript/setup.cfg delete mode 100644 examples/javascript/setup.py create mode 100644 examples/tutorial/pyproject.toml delete mode 100644 examples/tutorial/setup.cfg delete mode 100644 examples/tutorial/setup.py diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8d209048b8..8962490fe5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -102,14 +102,14 @@ First time setup .. code-block:: text - $ python3 -m venv env - $ . env/bin/activate + $ python3 -m venv .venv + $ . .venv/bin/activate - Windows .. code-block:: text - > py -3 -m venv env + > py -3 -m venv .venv > env\Scripts\activate - Upgrade pip and setuptools. diff --git a/docs/cli.rst b/docs/cli.rst index a3ea268eb3..ec2ea22d77 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -280,25 +280,25 @@ script. Activating the virtualenv will set the variables. .. group-tab:: Bash - Unix Bash, :file:`venv/bin/activate`:: + Unix Bash, :file:`.venv/bin/activate`:: $ export FLASK_APP=hello .. group-tab:: Fish - Fish, :file:`venv/bin/activate.fish`:: + Fish, :file:`.venv/bin/activate.fish`:: $ set -x FLASK_APP hello .. group-tab:: CMD - Windows CMD, :file:`venv\\Scripts\\activate.bat`:: + Windows CMD, :file:`.venv\\Scripts\\activate.bat`:: > set FLASK_APP=hello .. group-tab:: Powershell - Windows Powershell, :file:`venv\\Scripts\\activate.ps1`:: + Windows Powershell, :file:`.venv\\Scripts\\activate.ps1`:: > $env:FLASK_APP = "hello" @@ -438,24 +438,16 @@ Plugins Flask will automatically load commands specified in the ``flask.commands`` `entry point`_. This is useful for extensions that want to add commands when -they are installed. Entry points are specified in :file:`setup.py` :: +they are installed. Entry points are specified in :file:`pyproject.toml`: - from setuptools import setup - - setup( - name='flask-my-extension', - ..., - entry_points={ - 'flask.commands': [ - 'my-command=flask_my_extension.commands:cli' - ], - }, - ) +.. code-block:: toml + [project.entry-points."flask.commands"] + my-command = "my_extension.commands:cli" .. _entry point: https://packaging.python.org/tutorials/packaging-projects/#entry-points -Inside :file:`flask_my_extension/commands.py` you can then export a Click +Inside :file:`my_extension/commands.py` you can then export a Click object:: import click @@ -493,19 +485,12 @@ Create an instance of :class:`~cli.FlaskGroup` and pass it the factory:: def cli(): """Management script for the Wiki application.""" -Define the entry point in :file:`setup.py`:: +Define the entry point in :file:`pyproject.toml`: - from setuptools import setup +.. code-block:: toml - setup( - name='flask-my-extension', - ..., - entry_points={ - 'console_scripts': [ - 'wiki=wiki:cli' - ], - }, - ) + [project.scripts] + wiki = "wiki:cli" Install the application in the virtualenv in editable mode and the custom script is available. Note that you don't need to set ``--app``. :: diff --git a/docs/deploying/eventlet.rst b/docs/deploying/eventlet.rst index 243be5ebb3..3653c01ea8 100644 --- a/docs/deploying/eventlet.rst +++ b/docs/deploying/eventlet.rst @@ -34,8 +34,8 @@ Create a virtualenv, install your application, then install .. code-block:: text $ cd hello-app - $ python -m venv venv - $ . venv/bin/activate + $ python -m venv .venv + $ . .venv/bin/activate $ pip install . # install your application $ pip install eventlet diff --git a/docs/deploying/gevent.rst b/docs/deploying/gevent.rst index aae63e89e8..448b93e78c 100644 --- a/docs/deploying/gevent.rst +++ b/docs/deploying/gevent.rst @@ -33,8 +33,8 @@ Create a virtualenv, install your application, then install ``gevent``. .. code-block:: text $ cd hello-app - $ python -m venv venv - $ . venv/bin/activate + $ python -m venv .venv + $ . .venv/bin/activate $ pip install . # install your application $ pip install gevent diff --git a/docs/deploying/gunicorn.rst b/docs/deploying/gunicorn.rst index 93d11d3964..c50edc2326 100644 --- a/docs/deploying/gunicorn.rst +++ b/docs/deploying/gunicorn.rst @@ -30,8 +30,8 @@ Create a virtualenv, install your application, then install .. code-block:: text $ cd hello-app - $ python -m venv venv - $ . venv/bin/activate + $ python -m venv .venv + $ . .venv/bin/activate $ pip install . # install your application $ pip install gunicorn diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index eae973deb6..23e8227989 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -33,8 +33,8 @@ Create a virtualenv, install your application, then install .. code-block:: text $ cd hello-app - $ python -m venv venv - $ . venv/bin/activate + $ python -m venv .venv + $ . .venv/bin/activate $ pip install . # install your application $ pip install mod_wsgi @@ -89,6 +89,6 @@ mod_wsgi to drop to that user after starting. .. code-block:: text - $ sudo /home/hello/venv/bin/mod_wsgi-express start-server \ + $ sudo /home/hello/.venv/bin/mod_wsgi-express start-server \ /home/hello/wsgi.py \ --user hello --group hello --port 80 --processes 4 diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 2da5efe2d9..1f9d5eca00 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -29,8 +29,8 @@ Create a virtualenv, install your application, then install ``pyuwsgi``. .. code-block:: text $ cd hello-app - $ python -m venv venv - $ . venv/bin/activate + $ python -m venv .venv + $ . .venv/bin/activate $ pip install . # install your application $ pip install pyuwsgi diff --git a/docs/deploying/waitress.rst b/docs/deploying/waitress.rst index eb70e058a8..aeafb9f776 100644 --- a/docs/deploying/waitress.rst +++ b/docs/deploying/waitress.rst @@ -27,8 +27,8 @@ Create a virtualenv, install your application, then install .. code-block:: text $ cd hello-app - $ python -m venv venv - $ . venv/bin/activate + $ python -m venv .venv + $ . .venv/bin/activate $ pip install . # install your application $ pip install waitress diff --git a/docs/installation.rst b/docs/installation.rst index 8a338e182a..276fdc3dd4 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -85,7 +85,7 @@ environments. Create an environment ~~~~~~~~~~~~~~~~~~~~~ -Create a project folder and a :file:`venv` folder within: +Create a project folder and a :file:`.venv` folder within: .. tabs:: @@ -95,7 +95,7 @@ Create a project folder and a :file:`venv` folder within: $ mkdir myproject $ cd myproject - $ python3 -m venv venv + $ python3 -m venv .venv .. group-tab:: Windows @@ -103,7 +103,7 @@ Create a project folder and a :file:`venv` folder within: > mkdir myproject > cd myproject - > py -3 -m venv venv + > py -3 -m venv .venv .. _install-activate-env: @@ -119,13 +119,13 @@ Before you work on your project, activate the corresponding environment: .. code-block:: text - $ . venv/bin/activate + $ . .venv/bin/activate .. group-tab:: Windows .. code-block:: text - > venv\Scripts\activate + > .venv\Scripts\activate Your shell prompt will change to show the name of the activated environment. diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 13f8270ee8..12c4608335 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -42,19 +42,20 @@ You should then end up with something like that:: But how do you run your application now? The naive ``python yourapplication/__init__.py`` will not work. Let's just say that Python does not want modules in packages to be the startup file. But that is not -a big problem, just add a new file called :file:`setup.py` next to the inner -:file:`yourapplication` folder with the following contents:: +a big problem, just add a new file called :file:`pyproject.toml` next to the inner +:file:`yourapplication` folder with the following contents: - from setuptools import setup +.. code-block:: toml - setup( - name='yourapplication', - packages=['yourapplication'], - include_package_data=True, - install_requires=[ - 'flask', - ], - ) + [project] + name = "yourapplication" + dependencies = [ + "flask", + ] + + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" Install your application so it is importable: @@ -98,7 +99,7 @@ And this is what :file:`views.py` would look like:: You should then end up with something like that:: /yourapplication - setup.py + pyproject.toml /yourapplication __init__.py views.py diff --git a/docs/tutorial/deploy.rst b/docs/tutorial/deploy.rst index 436ed5e812..eb3a53ac5e 100644 --- a/docs/tutorial/deploy.rst +++ b/docs/tutorial/deploy.rst @@ -14,22 +14,13 @@ application. Build and Install ----------------- -When you want to deploy your application elsewhere, you build a -distribution file. The current standard for Python distribution is the -*wheel* format, with the ``.whl`` extension. Make sure the wheel library -is installed first: +When you want to deploy your application elsewhere, you build a *wheel* +(``.whl``) file. Install and use the ``build`` tool to do this. .. code-block:: none - $ pip install wheel - -Running ``setup.py`` with Python gives you a command line tool to issue -build-related commands. The ``bdist_wheel`` command will build a wheel -distribution file. - -.. code-block:: none - - $ python setup.py bdist_wheel + $ pip install build + $ python -m build --wheel You can find the file in ``dist/flaskr-1.0.0-py3-none-any.whl``. The file name is in the format of {project name}-{version}-{python tag} @@ -54,7 +45,7 @@ create the database in the instance folder. When Flask detects that it's installed (not in editable mode), it uses a different directory for the instance folder. You can find it at -``venv/var/flaskr-instance`` instead. +``.venv/var/flaskr-instance`` instead. Configure the Secret Key @@ -77,7 +68,7 @@ Create the ``config.py`` file in the instance folder, which the factory will read from if it exists. Copy the generated value into it. .. code-block:: python - :caption: ``venv/var/flaskr-instance/config.py`` + :caption: ``.venv/var/flaskr-instance/config.py`` SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' diff --git a/docs/tutorial/install.rst b/docs/tutorial/install.rst index f6820ebde9..9bb1234eda 100644 --- a/docs/tutorial/install.rst +++ b/docs/tutorial/install.rst @@ -1,11 +1,10 @@ Make the Project Installable ============================ -Making your project installable means that you can build a -*distribution* file and install that in another environment, just like -you installed Flask in your project's environment. This makes deploying -your project the same as installing any other library, so you're using -all the standard Python tools to manage everything. +Making your project installable means that you can build a *wheel* file and install that +in another environment, just like you installed Flask in your project's environment. +This makes deploying your project the same as installing any other library, so you're +using all the standard Python tools to manage everything. Installing also comes with other benefits that might not be obvious from the tutorial or as a new Python user, including: @@ -28,31 +27,25 @@ the tutorial or as a new Python user, including: Describe the Project -------------------- -The ``setup.py`` file describes your project and the files that belong -to it. +The ``pyproject.toml`` file describes your project and how to build it. -.. code-block:: python - :caption: ``setup.py`` +.. code-block:: toml + :caption: ``pyproject.toml`` - from setuptools import find_packages, setup + [project] + name = "flaskr" + version = "1.0.0" + dependencies = [ + "flask", + ] - setup( - name='flaskr', - version='1.0.0', - packages=find_packages(), - include_package_data=True, - install_requires=[ - 'flask', - ], - ) + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" -``packages`` tells Python what package directories (and the Python files -they contain) to include. ``find_packages()`` finds these directories -automatically so you don't have to type them out. To include other -files, such as the static and templates directories, -``include_package_data`` is set. Python needs another file named -``MANIFEST.in`` to tell what this other data is. +The setuptools build backend needs another file named ``MANIFEST.in`` to tell it about +non-Python files to include. .. code-block:: none :caption: ``MANIFEST.in`` @@ -62,9 +55,8 @@ files, such as the static and templates directories, graft flaskr/templates global-exclude *.pyc -This tells Python to copy everything in the ``static`` and ``templates`` -directories, and the ``schema.sql`` file, but to exclude all bytecode -files. +This tells the build to copy everything in the ``static`` and ``templates`` directories, +and the ``schema.sql`` file, but to exclude all bytecode files. See the official `Packaging tutorial `_ and `detailed guide `_ for more explanation of the files @@ -83,10 +75,10 @@ Use ``pip`` to install your project in the virtual environment. $ pip install -e . -This tells pip to find ``setup.py`` in the current directory and install -it in *editable* or *development* mode. Editable mode means that as you -make changes to your local code, you'll only need to re-install if you -change the metadata about the project, such as its dependencies. +This tells pip to find ``pyproject.toml`` in the current directory and install the +project in *editable* or *development* mode. Editable mode means that as you make +changes to your local code, you'll only need to re-install if you change the metadata +about the project, such as its dependencies. You can observe that the project is now installed with ``pip list``. diff --git a/docs/tutorial/layout.rst b/docs/tutorial/layout.rst index b6a09f0377..6f8e59f44d 100644 --- a/docs/tutorial/layout.rst +++ b/docs/tutorial/layout.rst @@ -41,7 +41,7 @@ The project directory will contain: * ``flaskr/``, a Python package containing your application code and files. * ``tests/``, a directory containing test modules. -* ``venv/``, a Python virtual environment where Flask and other +* ``.venv/``, a Python virtual environment where Flask and other dependencies are installed. * Installation files telling Python how to install your project. * Version control config, such as `git`_. You should make a habit of @@ -80,8 +80,8 @@ By the end, your project layout will look like this: │ ├── test_db.py │ ├── test_auth.py │ └── test_blog.py - ├── venv/ - ├── setup.py + ├── .venv/ + ├── pyproject.toml └── MANIFEST.in If you're using version control, the following files that are generated @@ -92,7 +92,7 @@ write. For example, with git: .. code-block:: none :caption: ``.gitignore`` - venv/ + .venv/ *.pyc __pycache__/ diff --git a/docs/tutorial/tests.rst b/docs/tutorial/tests.rst index cb60790cf5..f4744cda27 100644 --- a/docs/tutorial/tests.rst +++ b/docs/tutorial/tests.rst @@ -490,20 +490,18 @@ no longer exist in the database. Running the Tests ----------------- -Some extra configuration, which is not required but makes running -tests with coverage less verbose, can be added to the project's -``setup.cfg`` file. +Some extra configuration, which is not required but makes running tests with coverage +less verbose, can be added to the project's ``pyproject.toml`` file. -.. code-block:: none - :caption: ``setup.cfg`` +.. code-block:: toml + :caption: ``pyproject.toml`` - [tool:pytest] - testpaths = tests + [tool.pytest.ini_options] + testpaths = ["tests"] - [coverage:run] - branch = True - source = - flaskr + [tool.coverage.run] + branch = true + source = ["flaskr"] To run the tests, use the ``pytest`` command. It will find and run all the test functions you've written. @@ -514,7 +512,7 @@ the test functions you've written. ========================= test session starts ========================== platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0 - rootdir: /home/user/Projects/flask-tutorial, inifile: setup.cfg + rootdir: /home/user/Projects/flask-tutorial collected 23 items tests/test_auth.py ........ [ 34%] diff --git a/examples/javascript/.gitignore b/examples/javascript/.gitignore index 85a35845ad..a306afbc08 100644 --- a/examples/javascript/.gitignore +++ b/examples/javascript/.gitignore @@ -1,4 +1,4 @@ -venv/ +.venv/ *.pyc __pycache__/ instance/ diff --git a/examples/javascript/README.rst b/examples/javascript/README.rst index 23c7ce436d..697bb21760 100644 --- a/examples/javascript/README.rst +++ b/examples/javascript/README.rst @@ -23,8 +23,8 @@ Install .. code-block:: text - $ python3 -m venv venv - $ . venv/bin/activate + $ python3 -m venv .venv + $ . .venv/bin/activate $ pip install -e . diff --git a/examples/javascript/pyproject.toml b/examples/javascript/pyproject.toml new file mode 100644 index 0000000000..ce326ea715 --- /dev/null +++ b/examples/javascript/pyproject.toml @@ -0,0 +1,26 @@ +[project] +name = "js_example" +version = "1.1.0" +description = "Demonstrates making AJAX requests to Flask." +readme = "README.rst" +license = {text = "BSD-3-Clause"} +maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] +dependencies = ["flask"] + +[project.urls] +Documentation = "https://flask.palletsprojects.com/patterns/jquery/" + +[project.optional-dependencies] +test = ["pytest", "blinker"] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = ["error"] + +[tool.coverage.run] +branch = true +source = ["js_example", "tests"] diff --git a/examples/javascript/setup.cfg b/examples/javascript/setup.cfg deleted file mode 100644 index f509ddfe50..0000000000 --- a/examples/javascript/setup.cfg +++ /dev/null @@ -1,29 +0,0 @@ -[metadata] -name = js_example -version = 1.1.0 -url = https://flask.palletsprojects.com/patterns/jquery/ -license = BSD-3-Clause -maintainer = Pallets -maintainer_email = contact@palletsprojects.com -description = Demonstrates making AJAX requests to Flask. -long_description = file: README.rst -long_description_content_type = text/x-rst - -[options] -packages = find: -include_package_data = true -install_requires = - Flask - -[options.extras_require] -test = - pytest - blinker - -[tool:pytest] -testpaths = tests - -[coverage:run] -branch = True -source = - js_example diff --git a/examples/javascript/setup.py b/examples/javascript/setup.py deleted file mode 100644 index 606849326a..0000000000 --- a/examples/javascript/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() diff --git a/examples/tutorial/.gitignore b/examples/tutorial/.gitignore index 85a35845ad..a306afbc08 100644 --- a/examples/tutorial/.gitignore +++ b/examples/tutorial/.gitignore @@ -1,4 +1,4 @@ -venv/ +.venv/ *.pyc __pycache__/ instance/ diff --git a/examples/tutorial/README.rst b/examples/tutorial/README.rst index 1c745078bc..653c216729 100644 --- a/examples/tutorial/README.rst +++ b/examples/tutorial/README.rst @@ -23,13 +23,13 @@ default Git version is the main branch. :: Create a virtualenv and activate it:: - $ python3 -m venv venv - $ . venv/bin/activate + $ python3 -m venv .venv + $ . .venv/bin/activate Or on Windows cmd:: - $ py -3 -m venv venv - $ venv\Scripts\activate.bat + $ py -3 -m venv .venv + $ .venv\Scripts\activate.bat Install Flaskr:: diff --git a/examples/tutorial/pyproject.toml b/examples/tutorial/pyproject.toml new file mode 100644 index 0000000000..c86eb61f19 --- /dev/null +++ b/examples/tutorial/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "flaskr" +version = "1.0.0" +description = "The basic blog app built in the Flask tutorial." +readme = "README.rst" +license = {text = "BSD-3-Clause"} +maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] +dependencies = [ + "flask", +] + +[project.urls] +Documentation = "https://flask.palletsprojects.com/tutorial/" + +[project.optional-dependencies] +test = ["pytest"] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = ["error"] + +[tool.coverage.run] +branch = true +source = ["flaskr", "tests"] diff --git a/examples/tutorial/setup.cfg b/examples/tutorial/setup.cfg deleted file mode 100644 index d001093b48..0000000000 --- a/examples/tutorial/setup.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[metadata] -name = flaskr -version = 1.0.0 -url = https://flask.palletsprojects.com/tutorial/ -license = BSD-3-Clause -maintainer = Pallets -maintainer_email = contact@palletsprojects.com -description = The basic blog app built in the Flask tutorial. -long_description = file: README.rst -long_description_content_type = text/x-rst - -[options] -packages = find: -include_package_data = true -install_requires = - Flask - -[options.extras_require] -test = - pytest - -[tool:pytest] -testpaths = tests - -[coverage:run] -branch = True -source = - flaskr diff --git a/examples/tutorial/setup.py b/examples/tutorial/setup.py deleted file mode 100644 index 606849326a..0000000000 --- a/examples/tutorial/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() From 261e4a6cf287180b69c4db407791e43ce90e50ad Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 18 Jan 2023 10:31:08 -0800 Subject: [PATCH 032/119] fix flake8 bugbear errors --- .flake8 | 23 ++++++++++++----------- tests/test_appctx.py | 6 +++--- tests/test_basic.py | 4 ++-- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.flake8 b/.flake8 index 0980961647..a5ce884451 100644 --- a/.flake8 +++ b/.flake8 @@ -1,12 +1,12 @@ [flake8] -# B = bugbear -# E = pycodestyle errors -# F = flake8 pyflakes -# W = pycodestyle warnings -# B9 = bugbear opinions -# ISC = implicit str concat -select = B, E, F, W, B9, ISC -ignore = +extend-select = + # bugbear + B + # bugbear opinions + B9 + # implicit str concat + ISC +extend-ignore = # slice notation whitespace, invalid E203 # import at top, too many circular import fixes @@ -15,10 +15,11 @@ ignore = E501 # bare except, handled by bugbear B001 E722 - # bin op line break, invalid - W503 - # requires Python 3.10 + # zip with strict=, requires python >= 3.10 B905 + # string formatting opinion, B028 renamed to B907 + B028 + B907 # up to 88 allowed by bugbear B950 max-line-length = 80 per-file-ignores = diff --git a/tests/test_appctx.py b/tests/test_appctx.py index f5ca0bde42..aa3a8b4e5c 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -120,14 +120,14 @@ def cleanup(exception): @app.route("/") def index(): - raise Exception("dummy") + raise ValueError("dummy") - with pytest.raises(Exception, match="dummy"): + with pytest.raises(ValueError, match="dummy"): with app.app_context(): client.get("/") assert len(cleanup_stuff) == 1 - assert isinstance(cleanup_stuff[0], Exception) + assert isinstance(cleanup_stuff[0], ValueError) assert str(cleanup_stuff[0]) == "dummy" diff --git a/tests/test_basic.py b/tests/test_basic.py index d547012a5a..9aca667938 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1472,11 +1472,11 @@ def test_static_route_with_host_matching(): 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(Exception): + 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(Exception): + 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. From 3a35977d5fbcc424688d32154d250bc0bae918d6 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 19 Jan 2023 06:35:15 -0800 Subject: [PATCH 033/119] stop ignoring flake8 e402 --- .flake8 | 2 -- examples/javascript/js_example/__init__.py | 2 +- tests/test_apps/blueprintapp/__init__.py | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.flake8 b/.flake8 index a5ce884451..8f3b4fd4bc 100644 --- a/.flake8 +++ b/.flake8 @@ -9,8 +9,6 @@ extend-select = extend-ignore = # slice notation whitespace, invalid E203 - # import at top, too many circular import fixes - E402 # line length, handled by bugbear B950 E501 # bare except, handled by bugbear B001 diff --git a/examples/javascript/js_example/__init__.py b/examples/javascript/js_example/__init__.py index 068b2d98ed..0ec3ca215a 100644 --- a/examples/javascript/js_example/__init__.py +++ b/examples/javascript/js_example/__init__.py @@ -2,4 +2,4 @@ app = Flask(__name__) -from js_example import views # noqa: F401 +from js_example import views # noqa: E402, F401 diff --git a/tests/test_apps/blueprintapp/__init__.py b/tests/test_apps/blueprintapp/__init__.py index 4b05798531..ad594cf1da 100644 --- a/tests/test_apps/blueprintapp/__init__.py +++ b/tests/test_apps/blueprintapp/__init__.py @@ -2,8 +2,8 @@ app = Flask(__name__) app.config["DEBUG"] = True -from blueprintapp.apps.admin import admin -from blueprintapp.apps.frontend import frontend +from blueprintapp.apps.admin import admin # noqa: E402 +from blueprintapp.apps.frontend import frontend # noqa: E402 app.register_blueprint(admin) app.register_blueprint(frontend) From 99b34f7148b77d4b1311593590629c2bc8e8e772 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 18 Jan 2023 10:31:08 -0800 Subject: [PATCH 034/119] move and update flake8 config --- .flake8 | 25 ++++++++++++++++++++ examples/javascript/js_example/__init__.py | 2 +- setup.cfg | 27 ---------------------- tests/test_appctx.py | 6 ++--- tests/test_apps/blueprintapp/__init__.py | 4 ++-- tests/test_basic.py | 4 ++-- 6 files changed, 33 insertions(+), 35 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..8f3b4fd4bc --- /dev/null +++ b/.flake8 @@ -0,0 +1,25 @@ +[flake8] +extend-select = + # bugbear + B + # bugbear opinions + B9 + # implicit str concat + ISC +extend-ignore = + # slice notation whitespace, invalid + E203 + # line length, handled by bugbear B950 + E501 + # bare except, handled by bugbear B001 + E722 + # zip with strict=, requires python >= 3.10 + B905 + # string formatting opinion, B028 renamed to B907 + B028 + B907 +# up to 88 allowed by bugbear B950 +max-line-length = 80 +per-file-ignores = + # __init__ exports names + src/flask/__init__.py: F401 diff --git a/examples/javascript/js_example/__init__.py b/examples/javascript/js_example/__init__.py index 068b2d98ed..0ec3ca215a 100644 --- a/examples/javascript/js_example/__init__.py +++ b/examples/javascript/js_example/__init__.py @@ -2,4 +2,4 @@ app = Flask(__name__) -from js_example import views # noqa: F401 +from js_example import views # noqa: E402, F401 diff --git a/setup.cfg b/setup.cfg index ea7f66e20a..736bd50f27 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,33 +61,6 @@ source = src */site-packages -[flake8] -# B = bugbear -# E = pycodestyle errors -# F = flake8 pyflakes -# W = pycodestyle warnings -# B9 = bugbear opinions -# ISC = implicit str concat -select = B, E, F, W, B9, ISC -ignore = - # slice notation whitespace, invalid - E203 - # import at top, too many circular import fixes - E402 - # line length, handled by bugbear B950 - E501 - # bare except, handled by bugbear B001 - E722 - # bin op line break, invalid - W503 - # requires Python 3.10 - B905 -# up to 88 allowed by bugbear B950 -max-line-length = 80 -per-file-ignores = - # __init__ exports names - src/flask/__init__.py: F401 - [mypy] files = src/flask, tests/typing python_version = 3.7 diff --git a/tests/test_appctx.py b/tests/test_appctx.py index f5ca0bde42..aa3a8b4e5c 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -120,14 +120,14 @@ def cleanup(exception): @app.route("/") def index(): - raise Exception("dummy") + raise ValueError("dummy") - with pytest.raises(Exception, match="dummy"): + with pytest.raises(ValueError, match="dummy"): with app.app_context(): client.get("/") assert len(cleanup_stuff) == 1 - assert isinstance(cleanup_stuff[0], Exception) + assert isinstance(cleanup_stuff[0], ValueError) assert str(cleanup_stuff[0]) == "dummy" diff --git a/tests/test_apps/blueprintapp/__init__.py b/tests/test_apps/blueprintapp/__init__.py index 4b05798531..ad594cf1da 100644 --- a/tests/test_apps/blueprintapp/__init__.py +++ b/tests/test_apps/blueprintapp/__init__.py @@ -2,8 +2,8 @@ app = Flask(__name__) app.config["DEBUG"] = True -from blueprintapp.apps.admin import admin -from blueprintapp.apps.frontend import frontend +from blueprintapp.apps.admin import admin # noqa: E402 +from blueprintapp.apps.frontend import frontend # noqa: E402 app.register_blueprint(admin) app.register_blueprint(frontend) diff --git a/tests/test_basic.py b/tests/test_basic.py index d547012a5a..9aca667938 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1472,11 +1472,11 @@ def test_static_route_with_host_matching(): 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(Exception): + 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(Exception): + 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. From 0b4b61146ffca0f7dfc25f1b245df89442293335 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 20 Jan 2023 13:45:15 -0800 Subject: [PATCH 035/119] build, provenance, publish workflow --- .github/workflows/lock.yaml | 17 +++++--- .github/workflows/publish.yaml | 72 ++++++++++++++++++++++++++++++++++ .github/workflows/tests.yaml | 8 ++-- requirements/build.in | 1 + requirements/build.txt | 17 ++++++++ 5 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/publish.yaml create mode 100644 requirements/build.in create mode 100644 requirements/build.txt diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml index cd89f67c9d..c790fae5cb 100644 --- a/.github/workflows/lock.yaml +++ b/.github/workflows/lock.yaml @@ -1,18 +1,25 @@ -# This does not automatically close "stale" issues. Instead, it locks closed issues after 2 weeks of no activity. -# If there's a new issue related to an old one, we've found it's much easier to work on as a new issue. - name: 'Lock threads' +# Lock closed issues that have not received any further activity for +# two weeks. This does not close open issues, only humans may do that. +# We find that it is easier to respond to new issues with fresh examples +# rather than continuing discussions on old issues. on: schedule: - cron: '0 0 * * *' +permissions: + issues: write + pull-requests: write + +concurrency: + group: lock + jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v3 + - uses: dessant/lock-threads@c1b35aecc5cdb1a34539d14196df55838bb2f836 with: - github-token: ${{ github.token }} issue-inactive-days: 14 pr-inactive-days: 14 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000000..0ed4955916 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,72 @@ +name: Publish +on: + push: + tags: + - '*' +jobs: + build: + runs-on: ubuntu-latest + outputs: + hash: ${{ steps.hash.outputs.hash }} + steps: + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912 + with: + python-version: '3.x' + cache: 'pip' + cache-dependency-path: 'requirements/*.txt' + - run: pip install -r requirements/build.txt + # Use the commit date instead of the current date during the build. + - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV + - run: python -m build + # Generate hashes used for provenance. + - name: generate hash + id: hash + run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce + with: + path: ./dist + provenance: + needs: ['build'] + permissions: + actions: read + id-token: write + contents: write + # Can't pin with hash due to how this workflow works. + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.4.0 + with: + base64-subjects: ${{ needs.build.outputs.hash }} + create-release: + # Upload the sdist, wheels, and provenance to a GitHub release. They remain + # available as build artifacts for a while as well. + needs: ['provenance'] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a + - name: create release + run: > + gh release create --draft --repo ${{ github.repository }} + ${{ github.ref_name }} + *.intoto.jsonl/* artifact/* + env: + GH_TOKEN: ${{ github.token }} + publish-pypi: + needs: ['provenance'] + # Wait for approval before attempting to upload to PyPI. This allows reviewing the + # files in the draft release. + environment: 'publish' + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a + # Try uploading to Test PyPI first, in case something fails. + - uses: pypa/gh-action-pypi-publish@c7f29f7adef1a245bd91520e94867e5c6eedddcc + with: + password: ${{ secrets.TEST_PYPI_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + packages_dir: artifact/ + - uses: pypa/gh-action-pypi-publish@c7f29f7adef1a245bd91520e94867e5c6eedddcc + with: + password: ${{ secrets.PYPI_TOKEN }} + packages_dir: artifact/ diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 89e548dbad..79a56fcaa8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -35,10 +35,10 @@ jobs: - {name: 'PyPy', python: 'pypy-3.9', os: ubuntu-latest, tox: pypy39} - {name: 'Pallets Minimum Versions', python: '3.11', os: ubuntu-latest, tox: py311-min} - {name: 'Pallets Development Versions', python: '3.7', os: ubuntu-latest, tox: py37-dev} - - {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing} + - {name: Typing, python: '3.11', os: ubuntu-latest, tox: typing} steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912 with: python-version: ${{ matrix.python }} cache: 'pip' @@ -49,7 +49,7 @@ jobs: pip install -U setuptools python -m pip install -U pip - name: cache mypy - uses: actions/cache@v3.2.2 + uses: actions/cache@58c146cc91c5b9e778e71775dfe9bf1442ad9a12 with: path: ./.mypy_cache key: mypy|${{ matrix.python }}|${{ hashFiles('setup.cfg') }} diff --git a/requirements/build.in b/requirements/build.in new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/requirements/build.in @@ -0,0 +1 @@ +build diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 0000000000..a735b3d0d1 --- /dev/null +++ b/requirements/build.txt @@ -0,0 +1,17 @@ +# SHA1:80754af91bfb6d1073585b046fe0a474ce868509 +# +# This file is autogenerated by pip-compile-multi +# To update, run: +# +# pip-compile-multi +# +build==0.9.0 + # via -r requirements/build.in +packaging==23.0 + # via build +pep517==0.13.0 + # via build +tomli==2.0.1 + # via + # build + # pep517 From d93760d8bd6e367faefe9ad550f753e32ef9a12d Mon Sep 17 00:00:00 2001 From: Andrii Kolomoiets Date: Mon, 23 Jan 2023 17:01:49 +0200 Subject: [PATCH 036/119] Fix function argument name --- docs/views.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/views.rst b/docs/views.rst index 8937d7b55c..68a3462ad4 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -297,7 +297,7 @@ provide get (list) and post (create) methods. db.session.commit() return jsonify(item.to_json()) - def register_api(app, model, url): + def register_api(app, model, name): item = ItemAPI.as_view(f"{name}-item", model) group = GroupAPI.as_view(f"{name}-group", model) app.add_url_rule(f"/{name}/", view_func=item) From 94a23a3e24b6736ca3b54773de0ce384270c457a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:01:23 +0000 Subject: [PATCH 037/119] Bump actions/setup-python from 4.4.0 to 4.5.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/5ccb29d8773c3f3f653e1705f474dfaa8a06a912...d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/publish.yaml | 2 +- .github/workflows/tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 0ed4955916..edfdf55d5a 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -10,7 +10,7 @@ jobs: hash: ${{ steps.hash.outputs.hash }} steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912 + - uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 with: python-version: '3.x' cache: 'pip' diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 65b9877d3c..832535bb74 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -38,7 +38,7 @@ jobs: - {name: Typing, python: '3.11', os: ubuntu-latest, tox: typing} steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: actions/setup-python@5ccb29d8773c3f3f653e1705f474dfaa8a06a912 + - uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 with: python-version: ${{ matrix.python }} cache: 'pip' From 74c256872b5f0be65e9f71b91a73ac4b02647298 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 16:01:30 +0000 Subject: [PATCH 038/119] Bump actions/cache from 3.2.3 to 3.2.4 Bumps [actions/cache](https://github.com/actions/cache) from 3.2.3 to 3.2.4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/58c146cc91c5b9e778e71775dfe9bf1442ad9a12...627f0f41f6904a5b1efbaed9f96d9eb58e92e920) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 65b9877d3c..840f429158 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -49,7 +49,7 @@ jobs: pip install -U setuptools python -m pip install -U pip - name: cache mypy - uses: actions/cache@58c146cc91c5b9e778e71775dfe9bf1442ad9a12 + uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 with: path: ./.mypy_cache key: mypy|${{ matrix.python }}|${{ hashFiles('pyproject.toml') }} From 9abe28130d4f0aff48b4f9931ebc15bd7617f19f Mon Sep 17 00:00:00 2001 From: owgreen Date: Fri, 3 Feb 2023 00:43:02 +0900 Subject: [PATCH 039/119] fix doc --- docs/patterns/appfactories.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index 415c10fa47..a76e676f32 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -91,7 +91,7 @@ To run such an application, you can use the :command:`flask` command: .. code-block:: text - $ flask run --app hello run + $ flask --app hello run Flask will automatically detect the factory if it is named ``create_app`` or ``make_app`` in ``hello``. You can also pass arguments @@ -99,7 +99,7 @@ to the factory like this: .. code-block:: text - $ flask run --app hello:create_app(local_auth=True)`` + $ flask --app hello:create_app(local_auth=True) run`` Then the ``create_app`` factory in ``myapp`` is called with the keyword argument ``local_auth=True``. See :doc:`/cli` for more detail. From c1d01f69994e87489d04c3218a417f517ab6c88e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 04:42:52 +0000 Subject: [PATCH 040/119] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.12.0 → 23.1.0](https://github.com/psf/black/compare/22.12.0...23.1.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b1dee57df..d5e7a9c8ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: files: "^(?!examples/)" args: ["--application-directories", "src"] - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black - repo: https://github.com/PyCQA/flake8 From a15da89dbb4be39be09ed7e5b57a22acfb117e6d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 04:43:02 +0000 Subject: [PATCH 041/119] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_reqctx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index abfacb98bf..6c38b66186 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -227,7 +227,6 @@ def index(): 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): From 428d9430bc69031150a8d50ac8af17cc8082ea4e Mon Sep 17 00:00:00 2001 From: "Maxim G. Ivanov" Date: Thu, 9 Feb 2023 09:38:57 +0700 Subject: [PATCH 042/119] Fix command-line formatting --- docs/patterns/packages.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 13f8270ee8..3c7ae425b1 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -66,6 +66,8 @@ To use the ``flask`` command and run your application you need to set the ``--app`` option that tells Flask where to find the application instance: +.. code-block:: text + $ flask --app yourapplication run What did we gain from this? Now we can restructure the application a bit From dca8cf013b2c0086539697ae6e4515a1c819092c Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 8 Feb 2023 17:30:53 -0800 Subject: [PATCH 043/119] rewrite celery background tasks docs --- docs/patterns/celery.rst | 270 +++++++++++++++++++++++++++++---------- 1 file changed, 200 insertions(+), 70 deletions(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index 228a04a8aa..a236f6dd68 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -1,105 +1,235 @@ -Celery Background Tasks -======================= +Background Tasks with Celery +============================ -If your application has a long running task, such as processing some uploaded -data or sending email, you don't want to wait for it to finish during a -request. Instead, use a task queue to send the necessary data to another -process that will run the task in the background while the request returns -immediately. +If your application has a long running task, such as processing some uploaded data or +sending email, you don't want to wait for it to finish during a request. Instead, use a +task queue to send the necessary data to another process that will run the task in the +background while the request returns immediately. + +`Celery`_ is a powerful task queue that can be used for simple background tasks as well +as complex multi-stage programs and schedules. This guide will show you how to configure +Celery using Flask. Read Celery's `First Steps with Celery`_ guide to learn how to use +Celery itself. + +.. _Celery: https://celery.readthedocs.io +.. _First Steps with Celery: https://celery.readthedocs.io/en/latest/getting-started/first-steps-with-celery.html -Celery is a powerful task queue that can be used for simple background tasks -as well as complex multi-stage programs and schedules. This guide will show you -how to configure Celery using Flask, but assumes you've already read the -`First Steps with Celery `_ -guide in the Celery documentation. Install ------- -Celery is a separate Python package. Install it from PyPI using pip:: +Install Celery from PyPI, for example using pip: + +.. code-block:: text $ pip install celery -Configure ---------- -The first thing you need is a Celery instance, this is called the celery -application. It serves the same purpose as the :class:`~flask.Flask` -object in Flask, just for Celery. Since this instance is used as the -entry-point for everything you want to do in Celery, like creating tasks -and managing workers, it must be possible for other modules to import it. +Integrate Celery with Flask +--------------------------- -For instance you can place this in a ``tasks`` module. While you can use -Celery without any reconfiguration with Flask, it becomes a bit nicer by -subclassing tasks and adding support for Flask's application contexts and -hooking it up with the Flask configuration. +You can use Celery without any integration with Flask, but it's convenient to configure +it through Flask's config, and to let tasks access the Flask application. -This is all that is necessary to integrate Celery with Flask: +Celery uses similar ideas to Flask, with a ``Celery`` app object that has configuration +and registers tasks. While creating a Flask app, use the following code to create and +configure a Celery app as well. .. code-block:: python - from celery import Celery - - def make_celery(app): - celery = Celery(app.import_name) - celery.conf.update(app.config["CELERY_CONFIG"]) + from celery import Celery, Task - class ContextTask(celery.Task): - def __call__(self, *args, **kwargs): + 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.Task = ContextTask - return celery + 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 -The function creates a new Celery object, configures it with the broker -from the application config, updates the rest of the Celery config from -the Flask config and then creates a subclass of the task that wraps the -task execution in an application context. +This creates and returns a ``Celery`` app object. Celery `configuration`_ is taken from +the ``CELERY`` key in the Flask configuration. The Celery app is set as the default, so +that it is seen during each request. The ``Task`` subclass automatically runs task +functions with a Flask app context active, so that services like your database +connections are available. -.. note:: - Celery 5.x deprecated uppercase configuration keys, and 6.x will - remove them. See their official `migration guide`_. +.. _configuration: https://celery.readthedocs.io/en/stable/userguide/configuration.html -.. _migration guide: https://docs.celeryproject.org/en/stable/userguide/configuration.html#conf-old-settings-map. +Here's a basic ``example.py`` that configures Celery to use Redis for communication. We +enable a result backend, but ignore results by default. This allows us to store results +only for tasks where we care about the result. -An example task ---------------- - -Let's write a task that adds two numbers together and returns the result. We -configure Celery's broker and backend to use Redis, create a ``celery`` -application using the factory from above, and then use it to define the task. :: +.. code-block:: python from flask import Flask - flask_app = Flask(__name__) - flask_app.config.update(CELERY_CONFIG={ - 'broker_url': 'redis://localhost:6379', - 'result_backend': 'redis://localhost:6379', - }) - celery = make_celery(flask_app) + app = Flask(__name__) + app.config.from_mapping( + CELERY=dict( + broker_url="redis://localhost", + result_backend="redis://localhost", + task_ignore_result=True, + ), + ) + celery_app = celery_init_app(app) + +Point the ``celery worker`` command at this and it will find the ``celery_app`` object. + +.. code-block:: text + + $ celery -A example worker --loglevel INFO + +You can also run the ``celery beat`` command to run tasks on a schedule. See Celery's +docs for more information about defining schedules. + +.. code-block:: text + + $ celery -A example beat --loglevel INFO + + +Application Factory +------------------- + +When using the Flask application factory pattern, call the ``celery_init_app`` function +inside the factory. It sets ``app.extensions["celery"]`` to the Celery app object, which +can be used to get the Celery app from the Flask app returned by the factory. + +.. code-block:: python + + 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) + return app + +To use ``celery`` commands, Celery needs an app object, but that's no longer directly +available. Create a ``make_celery.py`` file that calls the Flask app factory and gets +the Celery app from the returned Flask app. + +.. code-block:: python + + from example import create_app + + flask_app = create_app() + celery_app = flask_app.extensions["celery"] + +Point the ``celery`` command to this file. + +.. code-block:: text + + $ celery -A make_celery worker --loglevel INFO + $ celery -A make_celery beat --loglevel INFO + - @celery.task() - def add_together(a, b): +Defining Tasks +-------------- + +Using ``@celery_app.task`` to decorate task functions requires access to the +``celery_app`` object, which won't be available when using the factory pattern. It also +means that the decorated tasks are tied to the specific Flask and Celery app instances, +which could be an issue during testing if you change configuration for a test. + +Instead, use Celery's ``@shared_task`` decorator. This creates task objects that will +access whatever the "current app" is, which is a similar concept to Flask's blueprints +and app context. This is why we called ``celery_app.set_default()`` above. + +Here's an example task that adds two numbers together and returns the result. + +.. code-block:: python + + from celery import shared_task + + @shared_task(ignore_result=False) + def add_together(a: int, b: int) -> int: return a + b -This task can now be called in the background:: +Earlier, we configured Celery to ignore task results by default. Since we want to know +the return value of this task, we set ``ignore_result=False``. On the other hand, a task +that didn't need a result, such as sending an email, wouldn't set this. + + +Calling Tasks +------------- + +The decorated function becomes a task object with methods to call it in the background. +The simplest way is to use the ``delay(*args, **kwargs)`` method. See Celery's docs for +more methods. + +A Celery worker must be running to run the task. Starting a worker is shown in the +previous sections. + +.. code-block:: python + + from flask import request - result = add_together.delay(23, 42) - result.wait() # 65 + @app.post("/add") + def start_add() -> dict[str, object]: + a = request.form.get("a", type=int) + b = request.form.get("b", type=int) + result = add_together.delay(a, b) + return {"result_id": result.id} -Run a worker ------------- +The route doesn't get the task's result immediately. That would defeat the purpose by +blocking the response. Instead, we return the running task's result id, which we can use +later to get the result. -If you jumped in and already executed the above code you will be -disappointed to learn that ``.wait()`` will never actually return. -That's because you also need to run a Celery worker to receive and execute the -task. :: - $ celery -A your_application.celery worker +Getting Results +--------------- + +To fetch the result of the task we started above, we'll add another route that takes the +result id we returned before. We return whether the task is finished (ready), whether it +finished successfully, and what the return value (or error) was if it is finished. + +.. code-block:: python + + from celery.result import AsyncResult + + @app.get("/result/") + def task_result(id: str) -> dict[str, object]: + result = AsyncResult(id) + return { + "ready": result.ready(), + "successful": result.successful(), + "value": result.result if result.ready() else None, + } + +Now you can start the task using the first route, then poll for the result using the +second route. This keeps the Flask request workers from being blocked waiting for tasks +to finish. + + +Passing Data to Tasks +--------------------- + +The "add" task above took two integers as arguments. To pass arguments to tasks, Celery +has to serialize them to a format that it can pass to other processes. Therefore, +passing complex objects is not recommended. For example, it would be impossible to pass +a SQLAlchemy model object, since that object is probably not serializable and is tied to +the session that queried it. + +Pass the minimal amount of data necessary to fetch or recreate any complex data within +the task. Consider a task that will run when the logged in user asks for an archive of +their data. The Flask request knows the logged in user, and has the user object queried +from the database. It got that by querying the database for a given id, so the task can +do the same thing. Pass the user's id rather than the user object. + +.. code-block:: python -The ``your_application`` string has to point to your application's package -or module that creates the ``celery`` object. + @shared_task + def generate_user_archive(user_id: str) -> None: + user = db.session.get(User, user_id) + ... -Now that the worker is running, ``wait`` will return the result once the task -is finished. + generate_user_archive.delay(current_user.id) From 3f195248dcd59f8eb08e282b7980dc04b97d7391 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 9 Feb 2023 10:50:57 -0800 Subject: [PATCH 044/119] add celery example --- docs/patterns/celery.rst | 7 ++ examples/celery/README.md | 27 +++++ examples/celery/make_celery.py | 4 + examples/celery/pyproject.toml | 11 ++ examples/celery/requirements.txt | 56 +++++++++ examples/celery/src/task_app/__init__.py | 39 +++++++ examples/celery/src/task_app/tasks.py | 23 ++++ .../celery/src/task_app/templates/index.html | 108 ++++++++++++++++++ examples/celery/src/task_app/views.py | 38 ++++++ 9 files changed, 313 insertions(+) create mode 100644 examples/celery/README.md create mode 100644 examples/celery/make_celery.py create mode 100644 examples/celery/pyproject.toml create mode 100644 examples/celery/requirements.txt create mode 100644 examples/celery/src/task_app/__init__.py create mode 100644 examples/celery/src/task_app/tasks.py create mode 100644 examples/celery/src/task_app/templates/index.html create mode 100644 examples/celery/src/task_app/views.py diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index a236f6dd68..2e9a43a731 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -14,6 +14,10 @@ Celery itself. .. _Celery: https://celery.readthedocs.io .. _First Steps with Celery: https://celery.readthedocs.io/en/latest/getting-started/first-steps-with-celery.html +The Flask repository contains `an example `_ +based on the information on this page, which also shows how to use JavaScript to submit +tasks and poll for progress and results. + Install ------- @@ -209,6 +213,9 @@ Now you can start the task using the first route, then poll for the result using second route. This keeps the Flask request workers from being blocked waiting for tasks to finish. +The Flask repository contains `an example `_ +using JavaScript to submit tasks and poll for progress and results. + Passing Data to Tasks --------------------- diff --git a/examples/celery/README.md b/examples/celery/README.md new file mode 100644 index 0000000000..91782019e2 --- /dev/null +++ b/examples/celery/README.md @@ -0,0 +1,27 @@ +Background Tasks with Celery +============================ + +This example shows how to configure Celery with Flask, how to set up an API for +submitting tasks and polling results, and how to use that API with JavaScript. See +[Flask's documentation about Celery](https://flask.palletsprojects.com/patterns/celery/). + +From this directory, create a virtualenv and install the application into it. Then run a +Celery worker. + +```shell +$ python3 -m venv .venv +$ . ./.venv/bin/activate +$ pip install -r requirements.txt && pip install -e . +$ celery -A make_celery worker --loglevel INFO +``` + +In a separate terminal, activate the virtualenv and run the Flask development server. + +```shell +$ . ./.venv/bin/activate +$ flask -A task_app --debug run +``` + +Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling +requests in the browser dev tools and the Flask logs. You can see the tasks submitting +and completing in the Celery logs. diff --git a/examples/celery/make_celery.py b/examples/celery/make_celery.py new file mode 100644 index 0000000000..f7d138e642 --- /dev/null +++ b/examples/celery/make_celery.py @@ -0,0 +1,4 @@ +from task_app import create_app + +flask_app = create_app() +celery_app = flask_app.extensions["celery"] diff --git a/examples/celery/pyproject.toml b/examples/celery/pyproject.toml new file mode 100644 index 0000000000..88ba6b960c --- /dev/null +++ b/examples/celery/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "flask-example-celery" +version = "1.0.0" +description = "Example Flask application with Celery background tasks." +readme = "README.md" +requires-python = ">=3.7" +dependencies = ["flask>=2.2.2", "celery[redis]>=5.2.7"] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/examples/celery/requirements.txt b/examples/celery/requirements.txt new file mode 100644 index 0000000000..b283401366 --- /dev/null +++ b/examples/celery/requirements.txt @@ -0,0 +1,56 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile pyproject.toml +# +amqp==5.1.1 + # via kombu +async-timeout==4.0.2 + # via redis +billiard==3.6.4.0 + # via celery +celery[redis]==5.2.7 + # via flask-example-celery (pyproject.toml) +click==8.1.3 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl + # flask +click-didyoumean==0.3.0 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.2.0 + # via celery +flask==2.2.2 + # via flask-example-celery (pyproject.toml) +itsdangerous==2.1.2 + # via flask +jinja2==3.1.2 + # via flask +kombu==5.2.4 + # via celery +markupsafe==2.1.2 + # via + # jinja2 + # werkzeug +prompt-toolkit==3.0.36 + # via click-repl +pytz==2022.7.1 + # via celery +redis==4.5.1 + # via celery +six==1.16.0 + # via click-repl +vine==5.0.0 + # via + # amqp + # celery + # kombu +wcwidth==0.2.6 + # via prompt-toolkit +werkzeug==2.2.2 + # via flask diff --git a/examples/celery/src/task_app/__init__.py b/examples/celery/src/task_app/__init__.py new file mode 100644 index 0000000000..dafff8aad8 --- /dev/null +++ b/examples/celery/src/task_app/__init__.py @@ -0,0 +1,39 @@ +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 diff --git a/examples/celery/src/task_app/tasks.py b/examples/celery/src/task_app/tasks.py new file mode 100644 index 0000000000..b6b3595d22 --- /dev/null +++ b/examples/celery/src/task_app/tasks.py @@ -0,0 +1,23 @@ +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} diff --git a/examples/celery/src/task_app/templates/index.html b/examples/celery/src/task_app/templates/index.html new file mode 100644 index 0000000000..4e1145cb8f --- /dev/null +++ b/examples/celery/src/task_app/templates/index.html @@ -0,0 +1,108 @@ + + + + + Celery Example + + +

Celery Example

+Execute background tasks with Celery. Submits tasks and shows results using JavaScript. + +
+

Add

+

Start a task to add two numbers, then poll for the result. +

+
+
+ +
+

Result:

+ +
+

Block

+

Start a task that takes 5 seconds. However, the response will return immediately. +

+ +
+

+ +
+

Process

+

Start a task that counts, waiting one second each time, showing progress. +

+
+ +
+

+ + + + diff --git a/examples/celery/src/task_app/views.py b/examples/celery/src/task_app/views.py new file mode 100644 index 0000000000..99cf92dc20 --- /dev/null +++ b/examples/celery/src/task_app/views.py @@ -0,0 +1,38 @@ +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} From ab93222bd6d4ea26e3aa832a0409489530f3f5e0 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 10 Feb 2023 09:58:48 -0800 Subject: [PATCH 045/119] point to app-scoped blueprint methods --- src/flask/blueprints.py | 70 ++++++++++++++++++++++------------------- src/flask/scaffold.py | 40 ++++++++++++++++++++++- 2 files changed, 77 insertions(+), 33 deletions(-) diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py index f6d62ba83f..eb6642358d 100644 --- a/src/flask/blueprints.py +++ b/src/flask/blueprints.py @@ -478,8 +478,11 @@ def add_url_rule( provide_automatic_options: t.Optional[bool] = None, **options: t.Any, ) -> None: - """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for - the :func:`url_for` function is prefixed with the name of the blueprint. + """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.") @@ -501,8 +504,8 @@ def add_url_rule( def app_template_filter( self, name: t.Optional[str] = None ) -> t.Callable[[T_template_filter], T_template_filter]: - """Register a custom template filter, available application wide. Like - :meth:`Flask.template_filter` but for a blueprint. + """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. @@ -518,9 +521,9 @@ def decorator(f: T_template_filter) -> T_template_filter: def add_app_template_filter( self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None ) -> None: - """Register a custom template filter, available application wide. Like - :meth:`Flask.add_template_filter` but for a blueprint. Works exactly - like the :meth:`app_template_filter` decorator. + """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. @@ -535,8 +538,8 @@ def register_template(state: BlueprintSetupState) -> None: def app_template_test( self, name: t.Optional[str] = None ) -> t.Callable[[T_template_test], T_template_test]: - """Register a custom template test, available application wide. Like - :meth:`Flask.template_test` but for a blueprint. + """Register a template test, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_test`. .. versionadded:: 0.10 @@ -554,9 +557,9 @@ def decorator(f: T_template_test) -> T_template_test: def add_app_template_test( self, f: ft.TemplateTestCallable, name: t.Optional[str] = None ) -> None: - """Register a custom template test, available application wide. Like - :meth:`Flask.add_template_test` but for a blueprint. Works exactly - like the :meth:`app_template_test` decorator. + """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 @@ -573,8 +576,8 @@ def register_template(state: BlueprintSetupState) -> None: def app_template_global( self, name: t.Optional[str] = None ) -> t.Callable[[T_template_global], T_template_global]: - """Register a custom template global, available application wide. Like - :meth:`Flask.template_global` but for a blueprint. + """Register a template global, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_global`. .. versionadded:: 0.10 @@ -592,9 +595,9 @@ def decorator(f: T_template_global) -> T_template_global: def add_app_template_global( self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None ) -> None: - """Register a custom template global, available application wide. Like - :meth:`Flask.add_template_global` but for a blueprint. Works exactly - like the :meth:`app_template_global` decorator. + """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 @@ -609,8 +612,8 @@ def register_template(state: BlueprintSetupState) -> None: @setupmethod def before_app_request(self, f: T_before_request) -> T_before_request: - """Like :meth:`Flask.before_request`. Such a function is executed - before each request, even if outside of a blueprint. + """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) @@ -621,8 +624,8 @@ def before_app_request(self, f: T_before_request) -> T_before_request: def before_app_first_request( self, f: T_before_first_request ) -> T_before_first_request: - """Like :meth:`Flask.before_first_request`. Such a function is - executed before the first request to the application. + """Register a function to run before the first request to the application is + handled by the worker. Equivalent to :meth:`.Flask.before_first_request`. .. deprecated:: 2.2 Will be removed in Flask 2.3. Run setup code when creating @@ -642,8 +645,8 @@ def before_app_first_request( @setupmethod def after_app_request(self, f: T_after_request) -> T_after_request: - """Like :meth:`Flask.after_request` but for a blueprint. Such a function - is executed after each request, even if outside of the blueprint. + """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) @@ -652,9 +655,8 @@ def after_app_request(self, f: T_after_request) -> T_after_request: @setupmethod def teardown_app_request(self, f: T_teardown) -> T_teardown: - """Like :meth:`Flask.teardown_request` but for a blueprint. Such a - function is executed when tearing down each request, even if outside of - the blueprint. + """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) @@ -665,8 +667,8 @@ def teardown_app_request(self, f: T_teardown) -> T_teardown: def app_context_processor( self, f: T_template_context_processor ) -> T_template_context_processor: - """Like :meth:`Flask.context_processor` but for a blueprint. Such a - function is executed each request, even if outside of the blueprint. + """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) @@ -677,8 +679,8 @@ def app_context_processor( def app_errorhandler( self, code: t.Union[t.Type[Exception], int] ) -> t.Callable[[T_error_handler], T_error_handler]: - """Like :meth:`Flask.errorhandler` but for a blueprint. This - handler is used for all requests, even if outside of the blueprint. + """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: @@ -691,7 +693,9 @@ def decorator(f: T_error_handler) -> T_error_handler: def app_url_value_preprocessor( self, f: T_url_value_preprocessor ) -> T_url_value_preprocessor: - """Same as :meth:`url_value_preprocessor` but application wide.""" + """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) ) @@ -699,7 +703,9 @@ def app_url_value_preprocessor( @setupmethod def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults: - """Same as :meth:`url_defaults` but application wide.""" + """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) ) diff --git a/src/flask/scaffold.py b/src/flask/scaffold.py index ebfc741f1a..7277b33ad3 100644 --- a/src/flask/scaffold.py +++ b/src/flask/scaffold.py @@ -561,6 +561,11 @@ def load_user(): 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 @@ -577,6 +582,11 @@ def after_request(self, f: T_after_request) -> T_after_request: ``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 @@ -606,6 +616,11 @@ def teardown_request(self, f: T_teardown) -> T_teardown: ``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 @@ -615,7 +630,15 @@ def context_processor( self, f: T_template_context_processor, ) -> T_template_context_processor: - """Registers a template context processor function.""" + """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 @@ -635,6 +658,11 @@ def url_value_preprocessor( 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 @@ -644,6 +672,11 @@ 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 @@ -667,6 +700,11 @@ def page_not_found(error): 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 From ba2b3094d1a8177059ea68853a48fcd5e90920fe Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 10 Feb 2023 10:50:48 -0800 Subject: [PATCH 046/119] fix test client arg for query string example --- docs/reqcontext.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 2b109770e5..70ea13e366 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -69,11 +69,12 @@ everything that runs in the block will have access to :data:`request`, populated with your test data. :: def generate_report(year): - format = request.args.get('format') + format = request.args.get("format") ... with app.test_request_context( - '/make_report/2017', data={'format': 'short'}): + "/make_report/2017", query_string={"format": "short"} + ): generate_report() If you see that error somewhere else in your code not related to From a6cd8f212e762b8f70e00f3341b27799c0fb657a Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 10 Feb 2023 14:48:02 -0800 Subject: [PATCH 047/119] document the lifecycle of a flask application and request --- docs/index.rst | 1 + docs/lifecycle.rst | 168 ++++++++++++++++++++++++++++++++++++++++++++ docs/reqcontext.rst | 5 +- 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 docs/lifecycle.rst diff --git a/docs/index.rst b/docs/index.rst index 983f612f3f..747749d053 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,6 +48,7 @@ community-maintained extensions to add even more functionality. config signals views + lifecycle appcontext reqcontext blueprints diff --git a/docs/lifecycle.rst b/docs/lifecycle.rst new file mode 100644 index 0000000000..2344d98ad7 --- /dev/null +++ b/docs/lifecycle.rst @@ -0,0 +1,168 @@ +Application Structure and Lifecycle +=================================== + +Flask makes it pretty easy to write a web application. But there are quite a few +different parts to an application and to each request it handles. Knowing what happens +during application setup, serving, and handling requests will help you know what's +possible in Flask and how to structure your application. + + +Application Setup +----------------- + +The first step in creating a Flask application is creating the application object. Each +Flask application is an instance of the :class:`.Flask` class, which collects all +configuration, extensions, and views. + +.. code-block:: python + + from flask import Flask + + app = Flask(__name__) + app.config.from_mapping( + SECRET_KEY="dev", + ) + app.config.from_prefixed_env() + + @app.route("/") + def index(): + return "Hello, World!" + +This is known as the "application setup phase", it's the code you write that's outside +any view functions or other handlers. It can be split up between different modules and +sub-packages, but all code that you want to be part of your application must be imported +in order for it to be registered. + +All application setup must be completed before you start serving your application and +handling requests. This is because WSGI servers divide work between multiple workers, or +can be distributed across multiple machines. If the configuration changed in one worker, +there's no way for Flask to ensure consistency between other workers. + +Flask tries to help developers catch some of these setup ordering issues by showing an +error if setup-related methods are called after requests are handled. In that case +you'll see this error: + + The setup method 'route' can no longer be called on the application. It has already + handled its first request, any changes will not be applied consistently. + Make sure all imports, decorators, functions, etc. needed to set up the application + are done before running it. + +However, it is not possible for Flask to detect all cases of out-of-order setup. In +general, don't do anything to modify the ``Flask`` app object and ``Blueprint`` objects +from within view functions that run during requests. This includes: + +- Adding routes, view functions, and other request handlers with ``@app.route``, + ``@app.errorhandler``, ``@app.before_request``, etc. +- Registering blueprints. +- Loading configuration with ``app.config``. +- Setting up the Jinja template environment with ``app.jinja_env``. +- Setting a session interface, instead of the default itsdangerous cookie. +- Setting a JSON provider with ``app.json``, instead of the default provider. +- Creating and initializing Flask extensions. + + +Serving the Application +----------------------- + +Flask is a WSGI application framework. The other half of WSGI is the WSGI server. During +development, Flask, through Werkzeug, provides a development WSGI server with the +``flask run`` CLI command. When you are done with development, use a production server +to serve your application, see :doc:`deploying/index`. + +Regardless of what server you're using, it will follow the :pep:`3333` WSGI spec. The +WSGI server will be told how to access your Flask application object, which is the WSGI +application. Then it will start listening for HTTP requests, translate the request data +into a WSGI environ, and call the WSGI application with that data. The WSGI application +will return data that is translated into an HTTP response. + +#. Browser or other client makes HTTP request. +#. WSGI server receives request. +#. WSGI server converts HTTP data to WSGI ``environ`` dict. +#. WSGI server calls WSGI application with the ``environ``. +#. Flask, the WSGI application, does all its internal processing to route the request + to a view function, handle errors, etc. +#. Flask translates View function return into WSGI response data, passes it to WSGI + server. +#. WSGI server creates and send an HTTP response. +#. Client receives the HTTP response. + + +Middleware +~~~~~~~~~~ + +The WSGI application above is a callable that behaves in a certain way. Middleware +is a WSGI application that wraps another WSGI application. It's a similar concept to +Python decorators. The outermost middleware will be called by the server. It can modify +the data passed to it, then call the WSGI application (or further middleware) that it +wraps, and so on. And it can take the return value of that call and modify it further. + +From the WSGI server's perspective, there is one WSGI application, the one it calls +directly. Typically, Flask is the "real" application at the end of the chain of +middleware. But even Flask can call further WSGI applications, although that's an +advanced, uncommon use case. + +A common middleware you'll see used with Flask is Werkzeug's +:class:`~werkzeug.middleware.proxy_fix.ProxyFix`, which modifies the request to look +like it came directly from a client even if it passed through HTTP proxies on the way. +There are other middleware that can handle serving static files, authentication, etc. + + +How a Request is Handled +------------------------ + +For us, the interesting part of the steps above is when Flask gets called by the WSGI +server (or middleware). At that point, it will do quite a lot to handle the request and +generate the response. At the most basic, it will match the URL to a view function, call +the view function, and pass the return value back to the server. But there are many more +parts that you can use to customize its behavior. + +#. WSGI server calls the Flask object, which calls :meth:`.Flask.wsgi_app`. +#. A :class:`.RequestContext` object is created. This converts the WSGI ``environ`` + dict into a :class:`.Request` object. It also creates an :class:`AppContext` object. +#. The :doc:`app context ` is pushed, which makes :data:`.current_app` and + :data:`.g` available. +#. The :data:`.appcontext_pushed` signal is sent. +#. The :doc:`request context ` is pushed, which makes :attr:`.request` and + :class:`.session` available. +#. The session is opened, loading any existing session data using the app's + :attr:`~.Flask.session_interface`, an instance of :class:`.SessionInterface`. +#. The URL is matched against the URL rules registered with the :meth:`~.Flask.route` + decorator during application setup. If there is no match, the error - usually a 404, + 405, or redirect - is stored to be handled later. +#. The :data:`.request_started` signal is sent. +#. Any :meth:`~.Flask.url_value_preprocessor` decorated functions are called. +#. Any :meth:`~.Flask.before_request` decorated functions are called. If any of + these function returns a value it is treated as the response immediately. +#. If the URL didn't match a route a few steps ago, that error is raised now. +#. The :meth:`~.Flask.route` decorated view function associated with the matched URL + is called and returns a value to be used as the response. +#. If any step so far raised an exception, and there is an :meth:`~.Flask.errorhandler` + decorated function that matches the exception class or HTTP error code, it is + called to handle the error and return a response. +#. Whatever returned a response value - a before request function, the view, or an + error handler, that value is converted to a :class:`.Response` object. +#. Any :func:`~.after_this_request` decorated functions are called, then cleared. +#. Any :meth:`~.Flask.after_request` decorated functions are called, which can modify + the response object. +#. The session is saved, persisting any modified session data using the app's + :attr:`~.Flask.session_interface`. +#. The :data:`.request_finished` signal is sent. +#. If any step so far raised an exception, and it was not handled by an error handler + function, it is handled now. HTTP exceptions are treated as responses with their + corresponding status code, other exceptions are converted to a generic 500 response. + The :data:`.got_request_exception` signal is sent. +#. The response object's status, headers, and body are returned to the WSGI server. +#. Any :meth:`~.Flask.teardown_request` decorated functions are called. +#. The :data:`.request_tearing_down` signal is sent. +#. The request context is popped, :attr:`.request` and :class:`.session` are no longer + available. +#. Any :meth:`~.Flask.teardown_appcontext` decorated functions are called. +#. The :data:`.appcontext_tearing_down` signal is sent. +#. The app context is popped, :data:`.current_app` and :data:`.g` are no longer + available. +#. The :data:`.appcontext_popped` signal is sent. + +There are even more decorators and customization points than this, but that aren't part +of every request lifecycle. They're more specific to certain things you might use during +a request, such as templates, building URLs, or handling JSON data. See the rest of this +documentation, as well as the :doc:`api` to explore further. diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 2b109770e5..70ea13e366 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -69,11 +69,12 @@ everything that runs in the block will have access to :data:`request`, populated with your test data. :: def generate_report(year): - format = request.args.get('format') + format = request.args.get("format") ... with app.test_request_context( - '/make_report/2017', data={'format': 'short'}): + "/make_report/2017", query_string={"format": "short"} + ): generate_report() If you see that error somewhere else in your code not related to From aa040c085c8c560616069f06c34cf796c10b05f1 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 10 Feb 2023 15:07:24 -0800 Subject: [PATCH 048/119] run latest black format --- src/flask/templating.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/flask/templating.py b/src/flask/templating.py index 25cc3f95e6..edbbe93017 100644 --- a/src/flask/templating.py +++ b/src/flask/templating.py @@ -134,7 +134,7 @@ def _render(app: "Flask", template: Template, context: t.Dict[str, t.Any]) -> st def render_template( template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]], - **context: t.Any + **context: t.Any, ) -> str: """Render a template by name with the given context. @@ -180,7 +180,7 @@ def generate() -> t.Iterator[str]: def stream_template( template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]], - **context: t.Any + **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 From 24df8fc89d5659d041b91b30a2ada9de49ec2264 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 15 Feb 2023 14:24:56 -0800 Subject: [PATCH 049/119] show 'run --debug' in docs Reverts commit 4d69165ab6e17fa754139d348cdfd9edacbcb999. Now that a release has this option, it's ok to show it in the docs. It had been reverted because the 2.2.x docs showed it before 2.2.3 was released. --- docs/cli.rst | 12 ++++++++++-- docs/config.rst | 12 ++++++------ docs/debugging.rst | 4 ++-- docs/quickstart.rst | 2 +- docs/server.rst | 2 +- docs/tutorial/factory.rst | 2 +- examples/celery/README.md | 2 +- examples/tutorial/README.rst | 2 +- 8 files changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 22484f1731..be5a0b701b 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -95,7 +95,7 @@ the ``--debug`` option. .. code-block:: console - $ flask --app hello --debug run + $ flask --app hello run --debug * Serving Flask app "hello" * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) @@ -103,6 +103,14 @@ the ``--debug`` option. * Debugger is active! * Debugger PIN: 223-456-919 +The ``--debug`` option can also be passed to the top level ``flask`` command to enable +debug mode for any command. The following two ``run`` calls are equivalent. + +.. code-block:: console + + $ flask --app hello --debug run + $ flask --app hello run --debug + Watch and Ignore Files with the Reloader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -550,7 +558,7 @@ a name such as "flask run". Click the *Script path* dropdown and change it to *Module name*, then input ``flask``. The *Parameters* field is set to the CLI command to execute along with any arguments. -This example uses ``--app hello --debug run``, which will run the development server in +This example uses ``--app hello run --debug``, which will run the development server in debug mode. ``--app hello`` should be the import or file with your Flask app. If you installed your project as a package in your virtualenv, you may uncheck the diff --git a/docs/config.rst b/docs/config.rst index 7cffc44bb2..9db2045f7a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -47,17 +47,17 @@ Debug Mode The :data:`DEBUG` config value is special because it may behave inconsistently if changed after the app has begun setting up. In order to set debug mode reliably, use the -``--debug`` option on the ``flask`` command. ``flask run`` will use the interactive -debugger and reloader by default in debug mode. +``--debug`` option on the ``flask`` or ``flask run`` command. ``flask run`` will use the +interactive debugger and reloader by default in debug mode. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug Using the option is recommended. While it is possible to set :data:`DEBUG` in your -config or code, this is strongly discouraged. It can't be read early by the ``flask run`` -command, and some systems or extensions may have already configured themselves based on -a previous value. +config or code, this is strongly discouraged. It can't be read early by the +``flask run`` command, and some systems or extensions may have already configured +themselves based on a previous value. Builtin Configuration Values diff --git a/docs/debugging.rst b/docs/debugging.rst index fb3604b036..18f4286758 100644 --- a/docs/debugging.rst +++ b/docs/debugging.rst @@ -43,7 +43,7 @@ The debugger is enabled by default when the development server is run in debug m .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug When running from Python code, passing ``debug=True`` enables debug mode, which is mostly equivalent. @@ -72,7 +72,7 @@ which can interfere. .. code-block:: text - $ flask --app hello --debug run --no-debugger --no-reload + $ flask --app hello run --debug --no-debugger --no-reload When running from Python: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 02dbc97835..ad9e3bc4e8 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -108,7 +108,7 @@ To enable debug mode, use the ``--debug`` option. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug * Serving Flask app 'hello' * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) diff --git a/docs/server.rst b/docs/server.rst index a34dfab5dd..d38aa12089 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -24,7 +24,7 @@ debug mode. .. code-block:: text - $ flask --app hello --debug run + $ flask --app hello run --debug This enables debug mode, including the interactive debugger and reloader, and then starts the server on http://localhost:5000/. Use ``flask run --help`` to see the diff --git a/docs/tutorial/factory.rst b/docs/tutorial/factory.rst index c8e2c5f4e0..39febd135f 100644 --- a/docs/tutorial/factory.rst +++ b/docs/tutorial/factory.rst @@ -137,7 +137,7 @@ follow the tutorial. .. code-block:: text - $ flask --app flaskr --debug run + $ flask --app flaskr run --debug You'll see output similar to this: diff --git a/examples/celery/README.md b/examples/celery/README.md index 91782019e2..038eb51eb6 100644 --- a/examples/celery/README.md +++ b/examples/celery/README.md @@ -19,7 +19,7 @@ In a separate terminal, activate the virtualenv and run the Flask development se ```shell $ . ./.venv/bin/activate -$ flask -A task_app --debug run +$ flask -A task_app run --debug ``` Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling diff --git a/examples/tutorial/README.rst b/examples/tutorial/README.rst index a7e12ca250..1c745078bc 100644 --- a/examples/tutorial/README.rst +++ b/examples/tutorial/README.rst @@ -48,7 +48,7 @@ Run .. code-block:: text $ flask --app flaskr init-db - $ flask --app flaskr --debug run + $ flask --app flaskr run --debug Open http://127.0.0.1:5000 in a browser. From 41d4f62909bb426c84e9d057151f7d734695320a Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 15 Feb 2023 14:20:33 -0800 Subject: [PATCH 050/119] release version 2.2.3 --- CHANGES.rst | 6 ++---- src/flask/__init__.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 79e66e956d..cd1a04c489 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,11 +1,9 @@ Version 2.2.3 ------------- -Unreleased +Released 2023-02-15 -- Autoescaping is now enabled by default for ``.svg`` files. Inside - templates this behavior can be changed with the ``autoescape`` tag. - :issue:`4831` +- Autoescape is enabled by default for ``.svg`` template files. :issue:`4831` - Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892` - Add ``--debug`` option to the ``flask run`` command. :issue:`4777` diff --git a/src/flask/__init__.py b/src/flask/__init__.py index 4bd5231146..463f55f255 100644 --- a/src/flask/__init__.py +++ b/src/flask/__init__.py @@ -42,7 +42,7 @@ from .templating import stream_template as stream_template from .templating import stream_template_string as stream_template_string -__version__ = "2.2.3.dev" +__version__ = "2.2.3" def __getattr__(name): From c4c7f504be222c3aca9062656498ec3c72e2b2ad Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 16 Feb 2023 06:27:25 -0800 Subject: [PATCH 051/119] update dependencies --- requirements/build.txt | 6 +++--- requirements/dev.txt | 28 +++++++++++++--------------- requirements/docs.txt | 20 ++++++++++---------- requirements/tests.txt | 14 ++++++++++---- requirements/typing.txt | 14 +++++++++----- src/flask/app.py | 4 ++-- 6 files changed, 47 insertions(+), 39 deletions(-) diff --git a/requirements/build.txt b/requirements/build.txt index a735b3d0d1..f8a3ce7582 100644 --- a/requirements/build.txt +++ b/requirements/build.txt @@ -5,13 +5,13 @@ # # pip-compile-multi # -build==0.9.0 +build==0.10.0 # via -r requirements/build.in packaging==23.0 # via build -pep517==0.13.0 +pyproject-hooks==1.0.0 # via build tomli==2.0.1 # via # build - # pep517 + # pyproject-hooks diff --git a/requirements/dev.txt b/requirements/dev.txt index 41b2619ce3..ccb1921ab5 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,9 +8,9 @@ -r docs.txt -r tests.txt -r typing.txt -build==0.9.0 +build==0.10.0 # via pip-tools -cachetools==5.2.0 +cachetools==5.3.0 # via tox cfgv==3.3.1 # via pre-commit @@ -24,37 +24,35 @@ colorama==0.4.6 # via tox distlib==0.3.6 # via virtualenv -filelock==3.8.2 +filelock==3.9.0 # via # tox # virtualenv -identify==2.5.11 +identify==2.5.18 # via pre-commit nodeenv==1.7.0 # via pre-commit -pep517==0.13.0 - # via build pip-compile-multi==2.6.1 # via -r requirements/dev.in -pip-tools==6.12.1 +pip-tools==6.12.2 # via pip-compile-multi -platformdirs==2.6.0 +platformdirs==3.0.0 # via # tox # virtualenv -pre-commit==2.20.0 +pre-commit==3.0.4 # via -r requirements/dev.in -pyproject-api==1.2.1 +pyproject-api==1.5.0 # via tox +pyproject-hooks==1.0.0 + # via build pyyaml==6.0 # via pre-commit -toml==0.10.2 - # via pre-commit -toposort==1.7 +toposort==1.9 # via pip-compile-multi -tox==4.0.16 +tox==4.4.5 # via -r requirements/dev.in -virtualenv==20.17.1 +virtualenv==20.19.0 # via # pre-commit # tox diff --git a/requirements/docs.txt b/requirements/docs.txt index b1e46bde86..5c0568ca65 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -5,13 +5,13 @@ # # pip-compile-multi # -alabaster==0.7.12 +alabaster==0.7.13 # via sphinx babel==2.11.0 # via sphinx certifi==2022.12.7 # via requests -charset-normalizer==2.1.1 +charset-normalizer==3.0.1 # via requests docutils==0.17.1 # via @@ -23,21 +23,21 @@ imagesize==1.4.1 # via sphinx jinja2==3.1.2 # via sphinx -markupsafe==2.1.1 +markupsafe==2.1.2 # via jinja2 -packaging==22.0 +packaging==23.0 # via # pallets-sphinx-themes # sphinx pallets-sphinx-themes==2.0.3 # via -r requirements/docs.in -pygments==2.13.0 +pygments==2.14.0 # via # sphinx # sphinx-tabs -pytz==2022.7 +pytz==2022.7.1 # via babel -requests==2.28.1 +requests==2.28.2 # via sphinx snowballstemmer==2.2.0 # via sphinx @@ -52,11 +52,11 @@ sphinx-issues==3.0.1 # via -r requirements/docs.in sphinx-tabs==3.3.1 # via -r requirements/docs.in -sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx -sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-htmlhelp==2.0.1 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx @@ -66,5 +66,5 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -urllib3==1.26.13 +urllib3==1.26.14 # via requests diff --git a/requirements/tests.txt b/requirements/tests.txt index aff42de283..15cf399db8 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -11,13 +11,19 @@ attrs==22.2.0 # via pytest blinker==1.5 # via -r requirements/tests.in -iniconfig==1.1.1 +exceptiongroup==1.1.0 # via pytest -packaging==22.0 +greenlet==2.0.2 ; python_version < "3.11" + # via -r requirements/tests.in +iniconfig==2.0.0 + # via pytest +packaging==23.0 # via pytest pluggy==1.0.0 # via pytest -pytest==7.2.0 +pytest==7.2.1 # via -r requirements/tests.in -python-dotenv==0.21.0 +python-dotenv==0.21.1 # via -r requirements/tests.in +tomli==2.0.1 + # via pytest diff --git a/requirements/typing.txt b/requirements/typing.txt index ad8dd594df..fbabf5087b 100644 --- a/requirements/typing.txt +++ b/requirements/typing.txt @@ -7,19 +7,23 @@ # cffi==1.15.1 # via cryptography -cryptography==38.0.4 +cryptography==39.0.1 # via -r requirements/typing.in -mypy==0.991 +mypy==1.0.0 # via -r requirements/typing.in -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via mypy pycparser==2.21 # via cffi +tomli==2.0.1 + # via mypy types-contextvars==2.4.7 # via -r requirements/typing.in types-dataclasses==0.6.6 # via -r requirements/typing.in -types-setuptools==65.6.0.2 +types-docutils==0.19.1.4 + # via types-setuptools +types-setuptools==67.3.0.1 # via -r requirements/typing.in -typing-extensions==4.4.0 +typing-extensions==4.5.0 # via mypy diff --git a/src/flask/app.py b/src/flask/app.py index 0ac4bbb5ae..ff6b097382 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -1248,7 +1248,7 @@ def __init__(self, *args, **kwargs): """ cls = self.test_client_class if cls is None: - from .testing import FlaskClient as cls # type: ignore + from .testing import FlaskClient as cls return cls( # type: ignore self, self.response_class, use_cookies=use_cookies, **kwargs ) @@ -1266,7 +1266,7 @@ def test_cli_runner(self, **kwargs: t.Any) -> "FlaskCliRunner": cls = self.test_cli_runner_class if cls is None: - from .testing import FlaskCliRunner as cls # type: ignore + from .testing import FlaskCliRunner as cls return cls(self, **kwargs) # type: ignore From 6650764e9719402de2aaa6f321bdec587699c6b2 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 23 Feb 2023 08:35:16 -0800 Subject: [PATCH 052/119] remove previously deprecated code --- CHANGES.rst | 18 ++ docs/api.rst | 6 - docs/config.rst | 69 +------- src/flask/__init__.py | 4 +- src/flask/app.py | 340 +------------------------------------ src/flask/blueprints.py | 106 +----------- src/flask/globals.py | 29 +--- src/flask/helpers.py | 35 +--- src/flask/json/__init__.py | 220 +++--------------------- src/flask/json/provider.py | 104 +----------- src/flask/scaffold.py | 15 -- tests/conftest.py | 1 - tests/test_async.py | 9 - tests/test_basic.py | 39 ----- tests/test_blueprints.py | 10 +- tests/test_helpers.py | 22 +-- 16 files changed, 81 insertions(+), 946 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0b8d2cfde7..64d5770cbb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,24 @@ Version 2.3.0 Unreleased +- Remove previously deprecated code. :pr:`4995` + + - The ``push`` and ``pop`` methods of the deprecated ``_app_ctx_stack`` and + ``_request_ctx_stack`` objects are removed. ``top`` still exists to give + extensions more time to update, but it will be removed. + - The ``FLASK_ENV`` environment variable, ``ENV`` config key, and ``app.env`` + property are removed. + - The ``session_cookie_name``, ``send_file_max_age_default``, ``use_x_sendfile``, + ``propagate_exceptions``, and ``templates_auto_reload`` properties on ``app`` + are removed. + - The ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and + ``JSONIFY_PRETTYPRINT_REGULAR`` config keys are removed. + - The ``app.before_first_request`` and ``bp.before_app_first_request`` decorators + are removed. + - ``json_encoder`` and ``json_decoder`` attributes on app and blueprint, and the + corresponding ``json.JSONEncoder`` and ``JSONDecoder`` classes, are removed. + - The ``json.htmlsafe_dumps`` and ``htmlsafe_dump`` functions are removed. + - Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. :pr:`4947` - Ensure subdomains are applied with nested blueprints. :issue:`4834` diff --git a/docs/api.rst b/docs/api.rst index afbe0b79e6..bf37700f2e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -270,12 +270,6 @@ HTML ``