This context provides a detailed guide on how to use the Litestar web framework for CRUD operations, testing with Pytest, SQLite database connection, and front-end Brython code.
Abstract
The context discusses two examples of using the Litestar web framework. The first example demonstrates a web application for managing user data, which is tested using Pytest. The second example involves writing a Litestar app that manages books in an SQLite database, with both the front-end and back-end written in Python. The front-end uses Brython (Browser Python). The context includes detailed code snippets and explanations for each step of the process. The code is broken down into sections, with explanations provided for each section. The context also includes information on running the Litestar app and tests, as well as references to relevant documentation and libraries.
Bullet points
The context provides a guide on using the Litestar web framework for CRUD operations, testing with Pytest, SQLite database connection, and front-end Brython code.
The first example demonstrates a web application for managing user data, which is tested using Pytest.
The second example involves writing a Litestar app that manages books in an SQLite database, with both the front-end and back-end written in Python.
The front-end uses Brython (Browser Python).
The context includes detailed code snippets and explanations for each step of the process.
The code is broken down into sections, with explanations provided for each section.
The context also includes information on running the Litestar app and tests.
References to relevant documentation and libraries are provided.
Introducing Litestar — A New Python Web Framework (Part 2)
How to use Litestar CRUD operations, testing with Pytest, SQLite database connection, and front-end Brython code.
In this second article on Litestar, I present two additional examples showing how to use the framework.
In the first example, we look at a web app that can manage user data, and we test it using Pytest.
Then, in the second example, we write a Litestar app that manages books in an SQLite database. Both the front-end and the back-end are written in Python. The front-end is written using Brython (as in Browser Python).
An example of using and testing basic data operations
In the code below, a simple web application is being set up with basic operations for managing user data.
Below is the step-by-step explanation of what’s happening in the above code:
[Lines 1–3] Importing Necessary Modules and Classes:
- Necessary libraries and modules, including Litestar and its HTTP method decorators ( @get, @post, @delete), as well as HTTPException, are imported.
[Lines 5–10] Defining the User Data Class:
- A data class named User is defined using the @dataclass decorator, with attributes user_id, name, age, and email to represent user data.
[Lines 12–15] Initializing Dummy User Store:
- A list named DUMMY_USER_STORE is initialized with two User instances to act as a simple in-memory database for storing user data.
[Lines 17–25] Creating a New User:
- A route handler named create_user() is defined to handle POST requests to the /user path.
- This handler checks if a user with the provided user_id already exists in DUMMY_USER_STORE. If so, it returns False. Otherwise, it adds the new user to the store and returns the user data.
[Lines 27–31] Listing All Users:
- A route handler named list_users() is defined to handle GET requests to the /users path.
- This handler simply returns all users present in DUMMY_USER_STORE.
[Lines 33–40] Retrieving a Specific User:
- A route handler named get_user() is defined to handle GET requests to the /user/{user_id:int} path, where user_id is a path parameter.
- This handler searches for a user with the specified user_id in DUMMY_USER_STORE. If found, it returns the user data; otherwise, it raises an HTTPException with a status code of 400 and a detail message indicating the user was not found.
[Lines 42–50] Deleting a Specific User:
- A route handler named delete_user() is defined to handle DELETE requests to the /user/{user_id:int} path, where user_id is a path parameter.
- This handler creates a temporary copy of DUMMY_USER_STORE, clears DUMMY_USER_STORE, and then re-adds all users except the one with the specified user_id to DUMMY_USER_STORE. It returns None to indicate successful deletion.
[Line 52] Application Initialization:
- An instance of Litestar named app is created.
- The route_handlers argument is supplied with a list of the defined route handlers create_user(), list_users(), get_user(), and delete_user() to register them with the application, so they will be used to handle incoming HTTP requests to the specified paths.
To summarize, the above code sets up a simple web application for managing user data with basic CRUD operations using the Litestar web framework.
The second piece of code comprises a set of test functions for use with Pytest to verify the behavior of the above application.
Here’s the breakdown of the code into steps:
Imports and Logging Configuration:
- The requests library is imported for sending HTTP requests, and the logging module is imported for logging purposes.
- The logging level for the ‘faker’ logger is set to ERROR to suppress log messages below the error level.
Constants Definition:
- HOST, PORT, and BASE_URL constants are defined to specify the base URL of the Litestar application being tested.
Test Listing Users:
- The test_list_users() function sends a GET request to the /users endpoint to list all users.
- It logs the response and checks if the status code is 200(OK).
Test Creating a New User:
- The test_create_new_user() function constructs a URL for the /user endpoint, defines a new user’s data, and sends a POST request with the user data to create a new user.
- It logs the response and checks if the status code is 201 (Created).
Test Retrieving a Specific User:
- The test_get_user() function constructs a URL for the /user/{user_id} endpoint using a known user ID and sends a GET request to retrieve the user’s data.
- It logs the retrieved user data, checks the user data against the known values, and verifies that the status code is 200(OK).
Test Retrieving a Non-existent User:
- The test_get_nonexistant_user() function constructs a URL for the /user/{user_id} endpoint using a non-existent user ID and sends a GET request to attempt to retrieve the user’s data.
- It checks that the status code is 400 (Bad Request) as the user does not exist.
Test Deleting a User:
- The test_delete_user() function constructs a URL for the /user/{user_id} endpoint using a known user ID and sends a DELETE request to remove the user.
- It logs the status code of the response and checks if the status code is 204 (No Content), indicating successful deletion.
These test functions are designed to interact with the above Litestar application, sending HTTP requests to its various endpoints and verifying the responses to ensure the application behaves as expected.
Do note that the Litestar framework also has a built-in testing capability. We are not using this option here. Instead, we are using the basic Pytest functionality.,
To run the Litestar app, we can write the following:
Then, to run the tests, we first enter the \tests directory, and then we run pytest (in a separate command line interface):
cd tests
pytest
The result can be seen below:
All the tests that we wrote were passed!
An example using DTOs, connecting to an SQLite database, and using a Brython front-end
In the following code, a Litestar web application is being set up to manage a collection of books, using an SQLite database via SQLAlchemy for data storage. It also configures templates and static file handling using the Jinja template engine.
Here’s a step-by-step breakdown of the code:
[Lines 1–20] Import Statements and Future Annotations:
- Necessary libraries and modules are imported.
- __future__ import is used to enable annotations for forward references.
[Lines 22–30] Database Model Definition:
- A base class Base is defined using SQLAlchemy’s declarative_base method.
- A Book class, inheriting from Base, is defined representing a book entity with fields like id, title, author, publisher, and genre.
[Lines 32–44] Database Connection Context Manager:
- An async context manager db_connection is defined to manage database connections, creating an async engine if it doesn’t exist and disposing of the engine once done.
[Line 46] Session Maker:
- An asynchronous session maker is created using async_sessionmaker() from SQLAlchemy.
[Lines 48–53] Data Transfer Object (DTO) Classes:
- ReadDTO, WriteDTO, and PatchDTO classes are defined for handling data transfer between the application and the database.
[Lines 55–57] Single Page Application (SPA) Handler:
- The spa route handler is defined for the root path (/), which returns a home.html template.
[Lines 59–107] CRUD Route Handlers:
- Various async route handlers are defined for creating, reading, updating, and deleting Book records in the database:
- get_books(): Lists all books.
- get_book(): Retrieves a book by its ID.
- create_book(): Creates a new book.
- update_book(): Updates an existing book by its ID.
- delete_book(): Deletes a book by its ID.
- Each handler interacts with the database using async sessions from the session maker, and utilizes the DTO classes for data handling.
[Lines 109–122] Application Initialization:
- An instance of Litestar named app is created, with the defined route handlers registered and the db_connection async context manager specified for managing the database connection lifecycle.
- The template_config argument is set to specify the directory for templates and to use the Jinja template engine.
- The static_files_config argument is set to specify the directory for static files and the path to serve them from.
- The TemplateConfig and StaticFilesConfig are used to configure the handling of templates and static files, respectively, within the Litestar application.
Next, we have some HTML code that is structured to create a webpage for managing the book database from the above Litestar app.
The page is structured to use modals for user interactions to add and edit books, and Brython to handle these interactions and update the database. It employs spectre.css for styling and layout, providing a clean and modern user interface.
Here’s a step-by-step description of the structure and functionality of the HTML page:
Document Setup:
- The document is defined as HTML with a character encoding of UTF-8.
- External stylesheets from spectre.css are linked for styling the webpage.
- External JavaScript files for Brython (a Python 3 implementation for client-side web programming) are linked.
Brython Initialization:
- The body element has an onload attribute to call the brython() function, initializing Brython when the page loads.
Main Content Area:
- A div element with class container is defined to hold the main content of the webpage.
- Inside the container, a set of columns is defined, within which a single column is set up to hold the main interactive elements.
Header Section:
- A header section labeled “Book Database” is created using the hero class from spectre.css.
Canvas Element:
- A div element with id canvas is defined, possibly intended to display the book database records.
Python Script Link:
- A script element is included with a src attribute pointing to a Python file (static/home.py), indicating the use of Brython to run Python code on the client side.
Add Book Button:
- A button labeled “Add Book” is created, which might be used to trigger the display of a modal for adding new books to the database.
Add Book Modal:
- A modal dialog for adding books is defined with input fields for the title, author, publisher, and genre of the new book.
- A submit button is included to presumably save the new book details.
Edit Book Modal:
- Another modal dialog for editing existing books is defined similarly to the add book modal, with the same input fields and a hidden input field to hold the id of the book being edited.
- A submit button is included to presumably save the edited book details.
Finally, we have the front-end Python script, utilizing the Brython library, that is used in the above HTML to implement the communication between the webpage and the Litestar app running on the back-end.
Here’s a step-by-step description of its structure and functionality:
[Lines 1–4] Initial Setup:
- The script imports necessary modules such as json, ajax, html, and others from the browser module.
- The base_url variable is defined to point to the local server.
[Lines 6–46] Fetching and Displaying Books:
- The get_all_books() function sends an AJAX GET request to fetch all books from the server.
- When the request is complete, the complete_get_all_books() function is triggered to handle the response.
- It clears the canvas element, checks if there are books, and creates a table to display the book data, with Edit and Delete buttons for each book.
[Lines 48–59] Adding Books:
- The click_add_book() function activates a modal for adding books.
- The click_add_book_submit() function collects the input data, sends an AJAX POST request to add a new book, and then triggers complete_add_book() to handle the response.
- complete_add_book() resets the modal and input fields, and calls get_all_books() to refresh the book list.
[Lines 71–100] Editing Books:
- The click_button_edit() function triggers an AJAX GET request to fetch the details of a specific book when the Edit button is clicked.
- complete_edit_get_book() processes the response, populates the edit modal with the book data, and shows the modal.
- click_edit_book_submit() collects the updated data, sends an AJAX PUT request to update the book, and then triggers complete_edit_book() to handle the response.
- complete_edit_book() hides the modal and refreshes the book list.
[Lines 102–108] Deleting Books:
- The click_button_delete() function sends an AJAX DELETE request to delete a specific book when the Delete button is clicked, triggering complete_delete_book() to handle the response.
- complete_delete_book() refreshes the book list.
[Lines 110–112] Modal Closing:
- The click_modal_close() function hides any active modals when the close button is clicked.
[Lines 114–118] Initial Actions:
- The script initially calls get_all_books() to populate the book list.
- It also binds the click events for the Add Book button and modal close buttons to their respective functions.
- Various functions are bound to click events, enabling interactions like opening modals, submitting new or updated book data, and deleting books.
So, essentially, the Brython code creates a dynamic front-end interface for managing a book database by handling CRUD (Create, Read, Update, Delete) operations through interactions with our Litestar back-end server.
Again, to run the Litestar app, first, make sure that the files are in the right directories: