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

  5. Contexts in Flask
    5

    View 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 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 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 Slide

  9. Request Handling - Overview
    9

    View Slide

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

    View 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 Slide

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

    View 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 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 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 Slide

  16. Request Handling - Recap
    16

    View Slide

  17. Example of the Application
    Context using pytest
    17

    View 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 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 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 Slide

  21. Error!
    (venv)$ pytest

    RuntimeError: Working outside of application context.
    21
    shell

    View Slide

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

    View 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 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 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 Slide

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

    View Slide

  27. Reference Material
    27

    View 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 Slide