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

Demystifying Flask's Application and Request Contexts with pytest

Patrick
October 14, 2020

Demystifying Flask's Application and Request Contexts with pytest

Presentation from the San Francisco Python Meetup on 10/14/2020

Patrick

October 14, 2020
Tweet

More Decks by Patrick

Other Decks in Programming

Transcript

  1. Demystifying
    Flask's
    Application and
    Request Contexts
    with pytest
    Patrick Kennedy
    SF Python Meetup - October 2020

    View full-size slide

  2. I’m Patrick Kennedy
    2
    Email: [email protected] | Twitter: patkennedy79
    Personal Blog: https://www.patricksoftwareblog.com
    Software Engineer:
    ● Real-time Applications
    ● Web Applications
    Programming 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
    ● Developing Web Applications with Python and Flask
    ○ https://testdriven.io/courses/learn-flask/ - released 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. Flask - Introduction
    ● Web framework written in Python
    ○ Author: Armin Ronacher
    ○ Core Maintainer: David Lord
    ○ Current release: 1.1.2
    ● “Micro” Framework
    ○ Provides core functionality for developing web applications
    ○ Additional functionality is available via extensions:
    ■ SQL database interface: Flask-SQLAlchemy
    ■ Form validation: Flask-WTF
    4
    https://palletsprojects.com/p/flask/

    View full-size slide

  5. Contexts in Flask
    5

    View full-size slide

  6. 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
    6
    * CLI - Command-Line Interface

    View full-size slide

  7. Request Processing in Flask
    7
    Flask Application
    Web Browser
    (Firefox, Chrome, etc.)
    Request: GET ‘/’
    Status Code: 200 (OK)
    Response (HTML, CSS)
    WSGI Server
    Web Server
    * WSGI - Web Server Gateway Interface

    View full-size slide

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

    View full-size slide

  9. Request Handling - Overview
    9

    View full-size slide

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

    View full-size slide

  11. Request Handling - Step 2
    11
    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

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

    View full-size slide

  13. Request Handling - Step 4
    13
    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

  14. 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'] })!")
    ...
    14
    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

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

    View full-size slide

  16. Request Handling - Recap
    16

    View full-size slide

  17. Example of the Application
    Context using pytest
    17

    View full-size slide

  18. 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
    18
    test_app.py
    “Python Testing with pytest” - Brian Okken:
    ● https://pragprog.com/titles/bopytest/

    View full-size slide

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

  20. 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
    20
    conftest.py
    Adding a log message to
    show when this fixture
    executes...

    View full-size slide

  21. Error!
    (venv)$ pytest

    RuntimeError: Working outside of application context.
    21
    shell

    View full-size slide

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

    View full-size slide

  23. 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 23
    Push the application context
    to the stack BEFORE
    accessing the logger
    Pop the application context

    View full-size slide

  24. 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!
    24
    Context manager

    View full-size slide

  25. Conclusion
    ● Application and Request Contexts provide the necessary data when
    processing requests or CLI commands in Flask:
    ○ Flask framework handles the processing of these contexts, but...
    ○ It’s important to know how to use these contexts to solve issues
    during development
    25

    View full-size slide

  26. Thank you!
    26
    https://testdriven.io/blog/flask-contexts/ https://testdriven.io/blog/flask-contexts-advanced/

    View full-size slide

  27. Reference Material
    27

    View full-size slide

  28. Reference Material
    28
    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