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. Demystifying
    Flask's Application
    and Request
    Contexts with
    pytest
    Patrick Kennedy
    FlaskCon 2020

    View full-size slide

  2. 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

    View full-size slide

  3. Outline
    ● How the Application and Request Contexts are processed when a
    request is handled in Flask
    ● Example of the Application Context using pytest
    3

    View full-size slide

  4. Contexts in Flask
    4

    View full-size slide

  5. 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

    View full-size slide

  6. 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

    View full-size slide

  7. 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

    View full-size slide

  8. Request Handling - Overview
    8

    View full-size slide

  9. Request Handling - Step 1
    9
    A request is received by the Web Server

    View full-size slide

  10. 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

    View full-size slide

  11. Request Handling - Step 3
    11
    The Flask application pushes the
    application context and request context
    onto their respective stacks

    View full-size slide

  12. 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!

    View full-size slide

  13. 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!

    View full-size slide

  14. Request Handling - Step 6
    14
    After the response is generated, the
    request context and application context are
    popped from their respective stacks

    View full-size slide

  15. Example of the Application
    Context using pytest
    15

    View full-size slide

  16. 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/

    View full-size slide

  17. 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.

    View full-size slide

  18. 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...

    View full-size slide

  19. Error!
    (venv)$ pytest

    RuntimeError: Working outside of application context.
    19
    shell

    View full-size slide

  20. What went wrong?
    20
    There is no application context on the
    stack!

    View full-size slide

  21. 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

    View full-size slide

  22. 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

    View full-size slide

  23. 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

    View full-size slide

  24. 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/

    View full-size slide

  25. Reference Material
    25

    View full-size slide

  26. 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

    View full-size slide