Flask App Automatically Get Request Again

Flask ii.0, which was released on May 11th, 2021, adds congenital-in back up for asynchronous routes, fault handlers, before and after asking functions, and teardown callbacks!

This article looks at Flask two.0's new async functionality and how to leverage it in your Flask projects.

This article assumes that you lot have prior experience with Flask. If you lot're interested in learning more about Flask, check out my class on how to build, examination, and deploy a Flask application:

Developing Web Applications with Python and Flask

  • Flask ii.0 Async
  • When Should Async Be Used?
  • Async Route Handler
  • Testing Async Routes
  • More Async Examples
  • Flask one.x Async
  • Conclusion

Flask 2.0 Async

Starting with Flask two.0, yous can create asynchronous route handlers using async/wait:

                                      import              asyncio              async              def              async_get_data              ():              await              asyncio              .              sleep              (              1              )              return              'Done!'              @app              .              route              (              "/data"              )              async              def              get_data              ():              data              =              await              async_get_data              ()              return              data                      

Creating asynchronous routes is as uncomplicated equally creating a synchronous road:

  1. You just demand to install Flask with the extra async via pip install "Flask[async]".
  2. Then, you can add the async keyword to your functions and utilize await.

How Does this Work?

The post-obit diagram illustrates how asynchronous code is executed in Flask ii.0:

Flask 2.x Asynchronous Diagram

In lodge to run asynchronous code in Python, an result loop is needed to run the coroutines. Flask 2.0 takes care of creating the asyncio event loop -- typically done with asyncio.run() -- for running the coroutines.

If yous're interested in learning more virtually the differences betwixt threads, multiprocessing, and async in Python, bank check out the Speeding Up Python with Concurrency, Parallelism, and asyncio post.

When an async route function is candy, a new sub-thread will exist created. Within this sub-thread, an asyncio issue loop will execute to run the route handler (coroutine).

This implementation leverages the asgiref library (specifically the AsyncToSync functionality) used by Django to run asynchronous lawmaking.

For more implementation specifics, refer to async_to_sync() in the Flask source code.

What makes this implementation corking is that information technology allows Flask to be run with any worker type (threads, gevent, eventlet, etc.).

Running asynchronous code prior to Flask 2.0 required creating a new asyncio consequence loop within each route handler, which necessitated running the Flask app using thread-based workers. More details to come later in this article...

Additionally, the use of asynchronous route handlers is backwards-compatible. Yous can utilize any combination of async and sync route handlers in a Flask app without any performance hit. This allows y'all to start prototyping a single async route handler correct away in an existing Flask project.

Why is ASGI not required?

Past design, Flask is a synchronous web framework that implements the WSGI (Spider web Server Gateway Interface) protocol.

WSGI is an interface between a spider web server and a Python-based web awarding. A WSGI (Web Server Gateway Interface) server (such as Gunicorn or uWSGI) is necessary for Python web applications since a web server cannot communicate directly with Python.

Want to learn more about WSGI?

Bank check out 'What is Gunicorn in Python?' and take a wait at the Building a Python Web Framework course.

When processing requests in Flask, each request is handled individually within a worker. The asynchronous functionality added to Flask 2.0 is always inside a single request being handled:

Flask 2.0 - Worker Running Async Event Loop

Keep in mind that even though asynchronous code can be executed in Flask, it's executed inside the context of a synchronous framework. In other words, while you can execute various async tasks in a unmarried request, each async chore must stop before a response gets sent back. Therefore, at that place are limited situations where asynchronous routes will actually be beneficial. There are other Python web frameworks that support ASGI (Asynchronous Server Gateway Interface), which supports asynchronous call stacks so that routes can run concurrently:

Framework Async Request Stack (e.1000., ASGI support) Async Routes
Quart Yes Yeah
Django >= 3.2 YES YES
FastAPI YES YES
Flask >= 2.0 NO YES

When Should Async Be Used?

While asynchronous execution tends to boss discussions and generate headlines, it's not the best approach for every state of affairs.

It's platonic for I/O-bound operations, when both of these are true:

  1. In that location'south a number of operations
  2. Each operation takes less than a few seconds to finish

For example:

  1. making HTTP or API calls
  2. interacting with a database
  3. working with the file system

Information technology's non appropriate for background and long-running tasks as well equally cpu-bound operations, like:

  1. Running motorcar learning models
  2. Processing images or PDFs
  3. Performing backups

Such tasks would be amend implemented using a task queue like Celery to manage separate long-running tasks.

Asynchronous HTTP calls

The asynchronous approach actually pays dividends when you lot demand to make multiple HTTP requests to an external website or API. For each request, there will be a significant amount of time needed for the response to be received. This wait time translates to your spider web app feeling slow or sluggish to your users.

Instead of making external requests i at a fourth dimension (via the requests package), yous can greatly speed upwards the process past leveraging async/look.

Synchronous vs. Asynchronous Call Diagram

In the synchronous arroyo, an external API telephone call (such equally a Become) is made and and then the awarding waits to get the response back. The amount of time it takes to get a response dorsum is called latency, which varies based on Internet connectivity and server response times. Latency in this case will probably be in the 0.ii - one.5 2d range per request.

In the asynchronous approach, an external API phone call is made and then processing continues on to make the next API call. Equally soon as a response is received from the external server, it's processed. This is a much more efficient use of resources.

In full general, asynchronous programming is perfect for situations like this where multiple external calls are fabricated and there'due south a lot of waiting for I/O responses.

Async Road Handler

aiohttp is a package that uses asyncio to create asynchronous HTTP clients and servers. If you're familiar with the requests package for performing HTTP calls synchronously, aiohttp is a similar package that focuses on asynchronous HTTP calls.

Hither's an example of aiohttp existence used in a Flask road:

                                      urls              =              [              'https://www.kennedyrecipes.com'              ,              'https://www.kennedyrecipes.com/breakfast/pancakes/'              ,              'https://www.kennedyrecipes.com/breakfast/honey_bran_muffins/'              ]              # Helper Functions              async              def              fetch_url              (              session              ,              url              ):              """Fetch the specified URL using the aiohttp session specified."""              response              =              await              session              .              go              (              url              )              return              {              'url'              :              response              .              url              ,              'condition'              :              response              .              status              }              # Routes              @app              .              road              (              '/async_get_urls_v2'              )              async              def              async_get_urls_v2              ():              """Asynchronously think the list of URLs."""              async              with              ClientSession              ()              as              session              :              tasks              =              []              for              url              in              urls              :              job              =              asyncio              .              create_task              (              fetch_url              (              session              ,              url              ))              tasks              .              suspend              (              task              )              sites              =              await              asyncio              .              gather              (              *              tasks              )              # Generate the HTML response              response              =              '<h1>URLs:</h1>'              for              site              in              sites              :              response              +=              f              "<p>URL:                            {              site              [              'url'              ]              }                              --- Condition Lawmaking:                            {              site              [              'status'              ]              }              </p>"              render              response                      

Y'all can find the source code for this case in the flask-async repo on GitLab.

The async_get_urls_v2() coroutine uses a mutual asyncio pattern:

  1. Create multiple asynchronous tasks (asyncio.create_task())
  2. Run them concurrently (asyncio.gather())

Testing Async Routes

You lot can exam an async route handler just similar you normally would with pytest since Flask handles all the async processing:

                                      @pytest              .              fixture              (              scope              =              'module'              )              def              test_client              ():              # Create a test client using the Flask application              with              app              .              test_client              ()              as              testing_client              :              yield              testing_client              # this is where the testing happens!              def              test_async_get_urls_v2              (              test_client              ):              """                              GIVEN a Flask test client                              WHEN the '/async_get_urls_v2' page is requested (GET)                              So check that the response is valid                              """              response              =              test_client              .              become              (              '/async_get_urls_v2'              )              assert              response              .              status_code              ==              200              assert              b              'URLs'              in              response              .              data                      

This is a basic check for a valid response from the /async_get_urls_v2 URL using the test_client fixture.

More Async Examples

Request callbacks can also be async in Flask 2.0:

                                      # Helper Functions              async              def              load_user_from_database              ():              """Mimics a long-running performance to load a user from an external database."""              app              .              logger              .              info              (              'Loading user from database...'              )              await              asyncio              .              sleep              (              1              )              async              def              log_request_status              ():              """Mimics a long-running functioning to log the request status."""              app              .              logger              .              info              (              'Logging status of request...'              )              await              asyncio              .              sleep              (              1              )              # Asking Callbacks              @app              .              before_request              async              def              app_before_request              ():              await              load_user_from_database              ()              @app              .              after_request              async              def              app_after_request              (              response              ):              await              log_request_status              ()              render              response                      

Fault handlers as well:

                                      # Helper Functions              async              def              send_error_email              (              error              ):              """Mimics a long-running functioning to log the fault."""              app              .              logger              .              info              (              'Logging status of error...'              )              await              asyncio              .              sleep              (              1              )              # Error Handlers              @app              .              errorhandler              (              500              )              async              def              internal_error              (              error              ):              await              send_error_email              (              error              )              render              '500 error'              ,              500                      

Flask 1.x Async

You lot can mimic Flask 2.0 async support in Flask i.x by using asyncio.run() to manage the asyncio event loop:

                                      # Helper Functions              async              def              fetch_url              (              session              ,              url              ):              """Fetch the specified URL using the aiohttp session specified."""              response              =              look              session              .              go              (              url              )              return              {              'url'              :              response              .              url              ,              'status'              :              response              .              status              }              async              def              get_all_urls              ():              """Retrieve the list of URLs asynchronously using aiohttp."""              async              with              ClientSession              ()              as              session              :              tasks              =              []              for              url              in              urls              :              job              =              asyncio              .              create_task              (              fetch_url              (              session              ,              url              ))              tasks              .              append              (              chore              )              results              =              expect              asyncio              .              gather              (              *              tasks              )              return              results              # Routes              @app              .              road              (              '/async_get_urls_v1'              )              def              async_get_urls_v1              ():              """Asynchronously retrieve the list of URLs (works in Flask 1.1.x when using threads)."""              sites              =              asyncio              .              run              (              get_all_urls              ())              # Generate the HTML response              response              =              '<h1>URLs:</h1>'              for              site              in              sites              :              response              +=              f              "<p>URL:                            {              site              [              'url'              ]              }                              --- Status Code:                            {              site              [              'status'              ]              }              </p>"              return              response                      

The get_all_urls() coroutine implements similar functionality that was covered in the async_get_urls_v2() road handler.

How does this work?

In order for the asyncio issue loop to properly run in Flask 1.10, the Flask application must exist run using threads (default worker type for Gunicorn, uWSGI, and the Flask development server):

Flask 1.x Asynchronous Diagram

Each thread will run an instance of the Flask awarding when a request is processed. Inside each thread, a dissever asyncio event loop is created for running any asynchronous operations.

Testing Coroutines

Y'all tin can utilise pytest-asyncio to test asynchronous lawmaking similar so:

                                      @pytest              .              marking              .              asyncio              async              def              test_fetch_url              ():              """                              GIVEN an `asyncio` event loop                              WHEN the `fetch_url()` coroutine is called                              And so check that the response is valid                              """              async              with              aiohttp              .              ClientSession              ()              every bit              session              :              effect              =              expect              fetch_url              (              session              ,              'https://www.kennedyrecipes.com/baked_goods/bagels/'              )              assert              str              (              issue              [              'url'              ])              ==              'https://www.kennedyrecipes.com/baked_goods/bagels/'              assert              int              (              event              [              'status'              ])              ==              200                      

This test function uses the @pytest.marking.asyncio decorator, which tells pytest to execute the coroutine as an asyncio task using the asyncio event loop.

Conclusion

The asynchronous support added in Flask ii.0 is an amazing feature! However, asynchronous code should but be used when it provides an advantage over the equivalent synchronous code. As yous saw, ane example of when asynchronous execution makes sense is when you have to make multiple HTTP calls within a route handler.

--

I performed some timing tests using the Flask 2.0 asynchronous function (async_get_urls_v2()) vs. the equivalent synchronous part. I performed ten calls to each route:

Type Average Time (seconds) Median Time (seconds)
Synchronous 4.071443 3.419016
Asynchronous 0.531841 0.406068

The asynchronous version is about 8x faster! So, if you have to make multiple external HTTP calls within a route handler, the increased complexity of using asyncio and aiohttp is definitely justified based on the significant decrease in execution time.

If you'd similar to larn more about Flask, be sure to check out my course -- Developing Spider web Applications with Python and Flask.

martinezexterais.blogspot.com

Source: https://testdriven.io/blog/flask-async/

0 Response to "Flask App Automatically Get Request Again"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel