From e609dddd604bd8c808145973d91dcb8bb56a9906 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 11 Nov 2021 16:11:43 -0800 Subject: [PATCH 1/3] drop Python 3.6 --- .github/workflows/tests.yaml | 1 - docs/cli.rst | 7 +++---- docs/extensiondev.rst | 6 +++--- docs/installation.rst | 4 +--- setup.cfg | 4 ++-- src/flask/json/__init__.py | 7 +------ tests/test_async.py | 11 ----------- tests/test_basic.py | 2 -- tox.ini | 2 +- 9 files changed, 11 insertions(+), 33 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2adf9a35..7654a223 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -31,7 +31,6 @@ jobs: - {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: '3.6', python: '3.6', os: ubuntu-latest, tox: py36} - {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37} - {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing} steps: diff --git a/docs/cli.rst b/docs/cli.rst index 6036cb2d..1f29d107 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -112,10 +112,9 @@ shell with the :func:`shell ` command. An application context will be active, and the app instance will be imported. :: $ flask shell - Python 3.6.2 (default, Jul 20 2017, 03:52:27) - [GCC 7.1.1 20170630] on linux - App: example - Instance: /home/user/Projects/hello/instance + Python 3.10.0 (default, Oct 27 2021, 06:59:51) [GCC 11.1.0] on linux + App: example [production] + Instance: /home/david/Projects/pallets/flask/instance >>> Use :meth:`~Flask.shell_context_processor` to add other automatic imports. diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 18b4fa7d..dbaf62cb 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -322,9 +322,9 @@ ecosystem remain consistent and compatible. `Official Pallets Themes`_. A link to the documentation or project website must be in the PyPI metadata or the readme. 7. For maximum compatibility, the extension should support the same - versions of Python that Flask supports. 3.6+ is recommended as of - 2020. Use ``python_requires=">= 3.6"`` in ``setup.py`` to indicate - supported versions. + versions of Python that Flask supports. 3.7+ is recommended as of + December 2021. Use ``python_requires=">= 3.7"`` in ``setup.py`` to + indicate supported versions. .. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask .. _mailinglist: https://mail.python.org/mailman/listinfo/flask diff --git a/docs/installation.rst b/docs/installation.rst index a5d105f7..6007bbb7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,9 +6,7 @@ Python Version -------------- We recommend using the latest version of Python. Flask supports Python -3.6 and newer. - -``async`` support in Flask requires Python 3.7+ for ``contextvars.ContextVar``. +3.7 and newer. Dependencies diff --git a/setup.cfg b/setup.cfg index e25c1e27..131b3ad3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ classifiers = packages = find: package_dir = = src include_package_data = true -python_requires = >= 3.6 +python_requires = >= 3.7 # Dependencies are in setup.py for GitHub's dependency graph. [options.packages.find] @@ -88,7 +88,7 @@ per-file-ignores = [mypy] files = src/flask -python_version = 3.6 +python_version = 3.7 allow_redefinition = True disallow_subclassing_any = True # disallow_untyped_calls = True diff --git a/src/flask/json/__init__.py b/src/flask/json/__init__.py index 10d5123a..9e553046 100644 --- a/src/flask/json/__init__.py +++ b/src/flask/json/__init__.py @@ -1,3 +1,4 @@ +import dataclasses import decimal import io import json as _json @@ -16,12 +17,6 @@ if t.TYPE_CHECKING: from ..app import Flask from ..wrappers import Response -try: - import dataclasses -except ImportError: - # Python < 3.7 - dataclasses = None # type: ignore - class JSONEncoder(_json.JSONEncoder): """The default JSON encoder. Handles extra types compared to the diff --git a/tests/test_async.py b/tests/test_async.py index 8276c4a8..c8bb9bf0 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -1,5 +1,4 @@ import asyncio -import sys import pytest @@ -79,7 +78,6 @@ def _async_app(): return app -@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7") @pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"]) def test_async_route(path, async_app): test_client = async_app.test_client() @@ -89,7 +87,6 @@ def test_async_route(path, async_app): assert b"POST" in response.get_data() -@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7") @pytest.mark.parametrize("path", ["/error", "/bp/error"]) def test_async_error_handler(path, async_app): test_client = async_app.test_client() @@ -97,7 +94,6 @@ def test_async_error_handler(path, async_app): assert response.status_code == 412 -@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7") def test_async_before_after_request(): app_first_called = False app_before_called = False @@ -154,10 +150,3 @@ def test_async_before_after_request(): test_client.get("/bp/") assert bp_before_called assert bp_after_called - - -@pytest.mark.skipif(sys.version_info >= (3, 7), reason="should only raise Python < 3.7") -def test_async_runtime_error(): - app = Flask(__name__) - with pytest.raises(RuntimeError): - app.async_to_sync(None) diff --git a/tests/test_basic.py b/tests/test_basic.py index 2cb96794..ce85ee71 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,6 +1,5 @@ import gc import re -import sys import time import uuid import weakref @@ -1323,7 +1322,6 @@ def test_jsonify_mimetype(app, req_ctx): assert rv.mimetype == "application/vnd.api+json" -@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires Python >= 3.7") def test_json_dump_dataclass(app, req_ctx): from dataclasses import make_dataclass diff --git a/tox.ini b/tox.ini index 6f6e804f..3cbe50f5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py3{11,10,9,8,7,6},pypy37 + py3{11,10,9,8,7},pypy3{8,7} py39-click7 style typing From 1b552d0b0163e1c1ae3d4895438a9ce2b2bc098e Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 11 Nov 2021 16:12:08 -0800 Subject: [PATCH 2/3] remove ContextVar compat --- docs/async-await.rst | 9 +++++++-- docs/installation.rst | 12 ++++++++++++ requirements/dev.txt | 2 +- requirements/tests.in | 2 +- requirements/tests.txt | 2 +- src/flask/app.py | 8 -------- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/async-await.rst b/docs/async-await.rst index 71e5f452..4c70f961 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -7,8 +7,7 @@ Using ``async`` and ``await`` Routes, error handlers, before request, after request, and teardown functions can all be coroutine functions if Flask is installed with the -``async`` extra (``pip install flask[async]``). It requires Python 3.7+ -where ``contextvars.ContextVar`` is available. This allows views to be +``async`` extra (``pip install flask[async]``). This allows views to be defined with ``async def`` and use ``await``. .. code-block:: python @@ -30,6 +29,12 @@ well as all the HTTP method handlers in views that inherit from the something like ``ValueError: set_wakeup_fd only works in main thread``, please upgrade to Python 3.9. +.. admonition:: Using ``async`` with greenlet + + When using gevent or eventlet to serve an application or patch the + runtime, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is + required. + Performance ----------- diff --git a/docs/installation.rst b/docs/installation.rst index 6007bbb7..8a338e18 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -49,6 +49,18 @@ use them if you install them. .. _watchdog: https://pythonhosted.org/watchdog/ +greenlet +~~~~~~~~ + +You may choose to use gevent or eventlet with your application. In this +case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is +required. + +These are not minimum supported versions, they only indicate the first +versions that added necessary features. You should use the latest +versions of each. + + Virtual environments -------------------- diff --git a/requirements/dev.txt b/requirements/dev.txt index b87571ff..33493af3 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -38,7 +38,7 @@ filelock==3.3.2 # via # tox # virtualenv -greenlet==1.1.2 +greenlet==1.1.2 ; python_version < "3.11" # via -r requirements/tests.in identify==2.3.3 # via pre-commit diff --git a/requirements/tests.in b/requirements/tests.in index 88fe5481..41936997 100644 --- a/requirements/tests.in +++ b/requirements/tests.in @@ -1,5 +1,5 @@ pytest asgiref blinker -greenlet +greenlet ; python_version < "3.11" python-dotenv diff --git a/requirements/tests.txt b/requirements/tests.txt index a86b6041..4e4247f5 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -10,7 +10,7 @@ attrs==21.2.0 # via pytest blinker==1.4 # via -r requirements/tests.in -greenlet==1.1.2 +greenlet==1.1.2 ; python_version < "3.11" # via -r requirements/tests.in iniconfig==1.1.1 # via pytest diff --git a/src/flask/app.py b/src/flask/app.py index 23b99e2c..7c4076c5 100644 --- a/src/flask/app.py +++ b/src/flask/app.py @@ -16,7 +16,6 @@ from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequestKeyError from werkzeug.exceptions import HTTPException from werkzeug.exceptions import InternalServerError -from werkzeug.local import ContextVar from werkzeug.routing import BuildError from werkzeug.routing import Map from werkzeug.routing import MapAdapter @@ -1621,13 +1620,6 @@ class Flask(Scaffold): "Install Flask with the 'async' extra in order to use async views." ) from None - # Check that Werkzeug isn't using its fallback ContextVar class. - if ContextVar.__module__ == "werkzeug.local": - raise RuntimeError( - "Async cannot be used with this combination of Python " - "and Greenlet versions." - ) - return asgiref_async_to_sync(func) def make_response(self, rv: ResponseReturnValue) -> Response: From df806c80359461fda0354460a46645d619e00942 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 11 Nov 2021 18:32:07 -0800 Subject: [PATCH 3/3] update docs about gevent/eventlet/greenlet --- docs/deploying/wsgi-standalone.rst | 181 +++++++++++++++++++++++------ 1 file changed, 145 insertions(+), 36 deletions(-) diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index 1339a625..823426ac 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -1,67 +1,171 @@ -Standalone WSGI Containers -========================== +Standalone WSGI Servers +======================= + +Most WSGI servers also provide HTTP servers, so they can run a WSGI +application and make it available externally. + +It may still be a good idea to run the server behind a dedicated HTTP +server such as Apache or Nginx. See :ref:`deploying-proxy-setups` if you +run into issues with that. -There are popular servers written in Python that contain WSGI applications and -serve HTTP. These servers stand alone when they run; you can proxy to them -from your web server. Note the section on :ref:`deploying-proxy-setups` if you -run into issues. Gunicorn -------- -`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork -worker model ported from Ruby's Unicorn project. It supports both `eventlet`_ -and `greenlet`_. Running a Flask application on this server is quite simple:: +`Gunicorn`_ is a WSGI and HTTP server for UNIX. To run a Flask +application, tell Gunicorn how to import your Flask app object. - $ gunicorn myproject:app +.. code-block:: text -`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``. -For example, to run a Flask application with 4 worker processes (``-w -4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``):: + $ gunicorn -w 4 -b 0.0.0.0:5000 your_project:app - $ gunicorn -w 4 -b 127.0.0.1:4000 myproject:app +The ``-w 4`` option uses 4 workers to handle 4 requests at once. The +``-b 0.0.0.0:5000`` serves the application on all interfaces on port +5000. -The ``gunicorn`` command expects the names of your application module or -package and the application instance within the module. If you use the -application factory pattern, you can pass a call to that:: +Gunicorn provides many options for configuring the server, either +through a configuration file or with command line options. Use +``gunicorn --help`` or see the docs for more information. + +The command expects the name of your module or package to import and +the application instance within the module. If you use the application +factory pattern, you can pass a call to that. + +.. code-block:: text + + $ gunicorn -w 4 -b 0.0.0.0:5000 "myproject:create_app()" + + +Async with Gevent or Eventlet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, Gunicorn provides workers using either `gevent`_ +or `eventlet`_. This is not the same as Python's ``async/await``, or the +ASGI server spec. + +When using either gevent or eventlet, greenlet>=1.0 is required, +otherwise context locals such as ``request`` will not work as expected. +When using PyPy, PyPy>=7.3.7 is required. + +To use gevent: + +.. code-block:: text + + $ gunicorn -k gevent -b 0.0.0.0:5000 your_project:app + +To use eventlet: + +.. code-block:: text + + $ gunicorn -k eventlet -b 0.0.0.0:5000 your_project:app - $ gunicorn "myproject:create_app()" .. _Gunicorn: https://gunicorn.org/ +.. _gevent: http://www.gevent.org/ .. _eventlet: https://eventlet.net/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ uWSGI --------- +----- -`uWSGI`_ is a fast application server written in C. It is very configurable -which makes it more complicated to setup than gunicorn. +`uWSGI`_ is a fast application server written in C. It is very +configurable, which makes it more complicated to setup than Gunicorn. +It also provides many other utilities for writing robust web +applications. To run a Flask application, tell Gunicorn how to import +your Flask app object. -Running `uWSGI HTTP Router`_:: +.. code-block:: text - $ uwsgi --http 127.0.0.1:5000 --module myproject:app + $ uwsgi --master -p 4 --http 0.0.0.0:5000 -w your_project:app -For a more optimized setup, see :doc:`configuring uWSGI and NGINX `. +The ``-p 4`` option uses 4 workers to handle 4 requests at once. The +``--http 0.0.0.0:5000`` serves the application on all interfaces on port +5000. + +uWSGI has optimized integration with Nginx and Apache instead of using +a standard HTTP proxy. See :doc:`configuring uWSGI and Nginx `. + + +Async with Gevent +~~~~~~~~~~~~~~~~~ + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, uWSGI provides workers using `gevent`_. It also +supports other async modes, see the docs for more information. This is +not the same as Python's ``async/await``, or the ASGI server spec. + +When using gevent, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +.. code-block:: text + + $ uwsgi --master --gevent 100 --http 0.0.0.0:5000 -w your_project:app .. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/ -.. _uWSGI HTTP Router: https://uwsgi-docs.readthedocs.io/en/latest/HTTP.html#the-uwsgi-http-https-router + Gevent -------- +------ -`Gevent`_ is a coroutine-based Python networking library that uses -`greenlet`_ to provide a high-level synchronous API on top of `libev`_ -event loop:: +Prefer using `Gunicorn`_ with Gevent workers rather than using Gevent +directly. Gunicorn provides a much more configurable and +production-tested server. See the section on Gunicorn above. + +`Gevent`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +It provides a WSGI server that can handle many connections at once +instead of one per worker process. + +`Eventlet`_, described below, is another library that does the same +thing. Certain dependencies you have, or other consideration, may affect +which of the two you choose to use + +To use gevent to serve your application, import its ``WSGIServer`` and +use it to run your ``app``. + +.. code-block:: python from gevent.pywsgi import WSGIServer - from yourapplication import app + from your_project import app - http_server = WSGIServer(('', 5000), app) + http_server = WSGIServer(("", 5000), app) http_server.serve_forever() -.. _Gevent: http://www.gevent.org/ -.. _greenlet: https://greenlet.readthedocs.io/en/latest/ -.. _libev: http://software.schmorp.de/pkg/libev.html + +Eventlet +-------- + +Prefer using `Gunicorn`_ with Eventlet workers rather than using +Eventlet directly. Gunicorn provides a much more configurable and +production-tested server. See the section on Gunicorn above. + +`Eventlet`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +It provides a WSGI server that can handle many connections at once +instead of one per worker process. + +`Gevent`_, described above, is another library that does the same +thing. Certain dependencies you have, or other consideration, may affect +which of the two you choose to use + +To use eventlet to serve your application, import its ``wsgi.server`` +and use it to run your ``app``. + +.. code-block:: python + + import eventlet + from eventlet import wsgi + from your_project import app + + wsgi.server(eventlet.listen(("", 5000), app) + Twisted Web ----------- @@ -69,7 +173,9 @@ Twisted Web `Twisted Web`_ is the web server shipped with `Twisted`_, a mature, non-blocking event-driven networking library. Twisted Web comes with a standard WSGI container which can be controlled from the command line using -the ``twistd`` utility:: +the ``twistd`` utility: + +.. code-block:: text $ twistd web --wsgi myproject.app @@ -79,13 +185,16 @@ This example will run a Flask application called ``app`` from a module named Twisted Web supports many flags and options, and the ``twistd`` utility does as well; see ``twistd -h`` and ``twistd web -h`` for more information. For example, to run a Twisted Web server in the foreground, on port 8080, with an -application from ``myproject``:: +application from ``myproject``: + +.. code-block:: text $ twistd -n web --port tcp:8080 --wsgi myproject.app .. _Twisted: https://twistedmatrix.com/trac/ .. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb + .. _deploying-proxy-setups: Proxy Setups