Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Demystifying Flask's Application and Request Contexts with pytest

Demystifying Flask's Application and Request Contexts with pytest

The application and request contexts are key parts of any Flask application, as they control which variables are and are not globally accessible. Understanding how each context works is important when developing Flask applications, and yet they are often misunderstood.

This talk dives into how the application and request contexts work when handling a request. The concept of proxies is discussed for providing thread-local access to the application and request contexts.

To really illustrate how the application context works, pytest will be utilized to show how the application context works outside of a request. This example shows a common error ("Working outside of application context") during testing and how it should be solved by pushing to the application context stack.

Patrick

July 04, 2020
Tweet

More Decks by Patrick

Other Decks in Programming

Transcript

  1. I’m Patrick Kennedy 2 Software Engineer: • Real-time Applications •

    Web Applications Languages: • C++, Python, JavaScript, HTML/CSS Author at TestDriven.io: • Learn Vue by Building and Deploying a CRUD App ◦ https://testdriven.io/courses/learn-vue/ - released in January 2020 • Learn Flask by Building and Deploying a Stock Portfolio App ◦ https://testdriven.io/courses/learn-flask/ - upcoming release in August 2020
  2. Outline • How the Application and Request Contexts are processed

    when a request is handled in Flask • Example of the Application Context using pytest 3
  3. What is a Context? • Contexts are used to keep

    track of the data needed to execute a software operation • In Flask, contexts provide the necessary data when processing requests or CLI* commands 5 * CLI - Command-Line Interface
  4. First point Web Server Flask Application Web Browser (Firefox, Chrome,

    etc.) Request: GET ‘/’ 200 (OK) Status Code Response: “Hello World!” Request Processing in Flask 6
  5. When a request is received, there are two contexts: 1.

    Application context - keeps track of the application-level data (configuration variables, logger, etc.) 2. Request context - keeps track of the request-level data (URL, HTTP method, headers, request data, session, etc.) Both of these contexts get created when a Flask application receives a request Contexts 7
  6. Request Handling - Step 2 10 A worker is spawned

    to handle the request • Worker can be a thread, process, or coroutine • Worker handles one request at a time
  7. Request Handling - Step 3 11 The Flask application pushes

    the application context and request context onto their respective stacks
  8. Request Handling - Steps 4 and 5 12 Proxies are

    made available to the view function Proxies are unique to each worker thanks to the stacks being context-local objects!
  9. Proxies @app.route('/add_item', methods=['GET', 'POST']) def add_item(application_context, request_context): if request_context.method ==

    'POST': # Save the form data to the database ... application_context.logger.info(f"Added new item ({ request_context.form['item_name'] })!") ... from flask import current_app, request @app.route('/add_item', methods=['GET', 'POST']) def add_item(): if request.method == 'POST': # Save the form data to the database ... current_app.logger.info(f"Added new item ({ request.form['item_name'] })!") ... 13 Instead of needing to pass in the contexts into each view function Flask provides proxies to the application and request contexts!
  10. Request Handling - Step 6 14 After the response is

    generated, the request context and application context are popped from their respective stacks
  11. pytest Example def test_index_page(test_client): """ GIVEN a Flask application WHEN

    the '/' page is requested (GET) THEN check the response is valid """ response = test_client.get('/') assert response.status_code == 200 assert b'Welcome!' in response.data 16 test_app.py “Python Testing with pytest” - Brian Okken: • https://pragprog.com/titles/bopytest/
  12. Fixture def test_index_page(test_client): """ GIVEN a Flask application WHEN the

    '/' page is requested (GET) THEN check the response is valid """ response = test_client.get('/') assert response.status_code == 200 assert b'Welcome!' in response.data import pytest from project import create_app @pytest.fixture(scope='module') def test_client(): flask_app = create_app() # Create a test client using the Flask application # configured for testing testing_client = flask_app.test_client() return testing_client 17 test_app.py conftest.py Fixtures initialize tests to a known state in order to run tests in a predictable and repeatable manner.
  13. Adding a log message... from flask import current_app @pytest.fixture(scope='module') def

    test_client(): flask_app = create_app() # Create a test client using the Flask application configured for testing testing_client = flask_app.test_client() current_app.logger.info('In the test_client fixture...') return testing_client 18 conftest.py Adding a log message to show when this fixture executes...
  14. How can this error be fixed? @pytest.fixture(scope='module') def test_client(): flask_app

    = create_app() # Create a test client using the Flask application configured for testing testing_client = flask_app.test_client() # Establish an application context ctx = flask_app.app_context() # Push the application context to the application context stack ctx.push() current_app.logger.info('In the test_client fixture...') # Pop the application context from the stack ctx.pop() return testing_client 21 Push the application context to the stack BEFORE accessing the logger Pop the application context
  15. Better Approach… Context Manager @pytest.fixture(scope='module') def test_client(): flask_app = create_app()

    # Create a test client using the Flask application configured for testing with flask_app.test_client() as testing_client: # Establish an application context before accessing the logger with flask_app.app_context(): current_app.logger.info('In the test_client fixture...') yield testing_client # this is where the testing happens! 22 Context manager
  16. Conclusion • The Application and Request Contexts provide the necessary

    data when processing requests or CLI commands • Flask provides the current_app and request proxies to avoid needing to pass in the contexts into each view or CLI function 23
  17. Thank you! 24 Patrick Kennedy Email: [email protected] Twitter: patkennedy79 Personal

    Blog: https://www.patricksoftwareblog.com Learn Flask by Building and Deploying a Stock Portfolio App: https://testdriven.io/courses/learn-flask/
  18. Reference Material 26 Context Locals (Werkzeug Documentation): • https://werkzeug.palletsprojects.com/en/1.0.x/local/ Application

    Context (Flask Documentation): • https://flask.palletsprojects.com/en/1.1.x/appcontext/ Request Context (Flask Documentation): • https://flask.palletsprojects.com/en/1.1.x/reqcontext/ pytest Documentation: • https://docs.pytest.org