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
Outline ● How the Application and Request Contexts are processed when a request is handled in Flask ● Example of the Application Context using pytest 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/
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
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
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
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
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!
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!
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/
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.
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...
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
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
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