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

Demystifying Flask's Application and Request Co...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Patrick Patrick
October 14, 2020

Demystifying Flask's Application and Request Contexts with pytest

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

Avatar for Patrick

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