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. 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
  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. 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/
  4. 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
  5. 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
  6. 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
  7. 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
  8. Request Handling - Step 3 12 The Flask application pushes

    the application context and request context onto their respective stacks
  9. 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!
  10. 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!
  11. Request Handling - Step 5 15 After the response is

    generated, the request context and application context are popped from their respective stacks
  12. 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/
  13. 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.
  14. 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...
  15. 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
  16. 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
  17. 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
  18. 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