Powerful tools for creating and testing APIs

In the world of modern web development, FastAPI has established itself as a powerful and fast framework for creating APIs. However, when working on large projects, developers often face the need to optimize routine processes, improve the code structure and simplify testing. In this article, we will consider a set of tools that will help solve these problems and significantly speed up development on FastAPI.

While going to many interviews, I noticed that many companies actively using FastAPI have developed their own libraries, but there are practically no similar tools with open access and a free license. As an experienced backend developer in Python and Django, I decided to adapt and integrate the most useful and popular solutions for developing REST APIs into FastAPI, based on my experience with Django.

Short term goal: Gather feedback from the community on this idea.

Long-term goal: improve the tool in open source by encouraging large companies to stop developing and maintaining their own proprietary code. Instead, we aim to create an ecosystem where companies not only use a common tool, but actively participate in its improvement by contributing to the open-source project.

Who will benefit from this?

  • For Python backend developers using or planning to use FastAPI

  • For teams working on medium to large projects on FastAPI

  • For developers who want to improve the structure of their FastAPI projects and speed up the development process

  • For those looking for effective tools for testing FastAPI applications

Why do we need this toolkit?

FastAPI Accelerator is an open-source toolkit built on best practices for REST API development.

The main goal of the presented toolkit is to speed up and simplify the development of projects on FastAPI.

This is achieved by:

  1. Detailed and good documentation.

  2. Providing reusable code for common tasks.

  3. Implementation of a universal manager for working with RDBMS.

  4. ViewSet implementations for quickly creating views with basic business logic.

  5. JWT authentication integrations.

  6. Addition of a convenient admin panel.

  7. Simplify writing and running integration tests for APIs.

  8. Optimizations for working with Alembic for managing migrations in production and test environments.

All these components are interconnected and complement each other, automating routine tasks.

Tool structure

Let's take a look at the main components of our toolkit:

fastapi_accelerator/
├── db/             # Логика взаимодействия с РСУБД
├── pattern/        # Шаблоны для проектов
├── testutils/      # Утилиты для тестирования FastAPI
├── cache.py        # Реализация кеширования
├── auth_jwt.py     # Аутентификация по JWT
├── exception.py    # Обработка исключений
├── middleware.py   # Middleware компоненты
├── paginator.py    # Реализация пагинации
├── timezone.py     # Работа с временными зонами
├── viewset.py      # Реализация ViewSet
└── utils.py        # Общие утилиты

FastAPI Project Structures

Proper project organization is key to its scalability and ease of support. Here is an example of the recommended FastAPI project structure using FastAPI Accelerator:

Проект/
│
├── app/
│   ├── __init__.py
│   ├── utils.py                # Пере используемый функционал для проекта
│   ├── core/                   # Содержит основные модули, такие как конфигурация, безопасность и общие зависимости.
│   │   ├── __init__.py
│   │   ├── settings_local.py   # Локальные значения для настроек, не должны быть в git, создавать непосредственно на сервере
│   │   ├── config.py           # Настройки проекта которые не зависят от внешних настроек
│   │   ├── security.py         # Логика безопасности проекта
│   │   ├── db.py               # Настройки и сессии базы данных.
│   │   ├── cache.py            # Настройки кеширования
│   │   └── dependencies.py
│   │
│   ├── api/                    # Содержит все API эндпоинты, разделенные по версиям.
│   │   ├── __init__.py
│   │   └── v1/
│   │       ├── __init__.py
│   │       ├── router.py       # Содержит обработчики запросов для указанной версии api
│   │       │
│   │       ├── static/         # Содержит файлы статики, если они нужны
│   │       │   ├── js
│   │       │   ├── css
│   │       │   ├── img
│   │       │   └── html
│   │       │
│   │       ├── logic/          # Содержит бизнес логику
│   │       │   ├── __init__.py
│   │       │   ├── users.py
│   │       │   └── items.py
│   │       │
│   │       ├── schemas/        # Pydantic модели для валидации запросов и ответов.
│   │       │   ├── __init__.py
│   │       │   ├── user.py
│   │       │   └── item.py
│   │       │
│   │       ├── crud/           # Функции для работы с базой данных (Create, Read, Update, Delete).
│   │       │   ├── __init__.py
│   │       │   ├── user.py
│   │       │   └── item.py
│   │       │
│   │       └── tests/          # Директория для тестов.
│   │           ├── __init__.py
│   │           ├── test_users.py
│   │           └── test_items.py
│   │
│   ├── models/             # Определения моделей базы данных (например, SQLAlchemy модели).
│   │    ├── __init__.py
│   │    ├── user.py
│   │    └── item.py
│   │
│   └── fixture/          # Хранит фикстуры для тестирования этого проекта
│       ├── __init__.py
│       ├── items_v1.py   # Тестовые записи для БД
│       └── utils.py      # Переиспользуемые фикстуры для тестов
│
├── fastapi_accelerator/               # Submodule для переиспользовать
│
├── alembic/              # Директория для миграций базы данных.
│   ├── versions/         # Папка с миграциями
│   │   ├── __init__.py
│   │   └── 0001_init.py  # Файл с миграцией
│   └── env.py            # Настройки для alembic
│
├─ conf/                            # Файлы конфигурации для prod
│   ├── settings_local.example.py   # Пример для создания settings_local.py
│   └── Dockerfile                  # Файл для prod
│
├── pytest.ini          # Конфигурация для pytest
├── conftest.py         # Настройки выполнения тестов
│
├── .gitignore          # Какие игнорировать файлы и папки в git
├── .gitlab-ci.yml      # Настройки CI pipeline
│
├── pyproject.toml      # Настройки Poetry
│
├── Makefile            # Переиспользуемые bash команды
│
├── README.md           # Описание проекта
├── CHANGELOG.md        # Изменения в проекте
├── version.toml        # Версия проекта
│
├── alembic.ini         # Конфигурации для alembic
│
├── DockerfileDev       # Файл для создания dev контейнера с APP
├── docker-compose.yml  # Используется для сборки dev окружения
│
├── admin_panel.py      # Админ панель
│
└── main.py             # Точка входа в приложение, где создается экземпляр FastAPI.

Connecting to FastAPI

File main.py:

from fastapi import FastAPI
from fastapi_accelerator.pattern.pattern_fastapi import base_pattern
from app.core.config import BASE_DIR_PROJECT, DEBUG, SECRET_KEY
from fastapi_accelerator.timezone import moscow_tz
from app.core.db import DatabaseManager
from app.core.security import AuthJWT

import app.api.v1.router as RouterV1

app = FastAPI()

# Паттерн для проекта
base_pattern(
    app,
    routers=(RouterV1.router,),
    timezone=moscow_tz,
    cache_status=True,
    debug=DEBUG,
    base_dir=BASE_DIR_PROJECT,
    database_manager=DatabaseManager,
    secret_key=SECRET_KEY,
)

# Подключить аутентификацию по JWT
AuthJWT.mount_auth(app)

Main components

Base Pattern

Function base_pattern adds a lot of useful features to appincluding:

  • Filling state and other information at app.

  • Permission CORS.

  • Connecting routers with support ViewSet.

  • Adding a method healthcheck.

  • Middleware to debug the execution time of API requests.

  • Detailed output for HTTP exceptions.

DatabaseManager

DatabaseManager – is a universal tool for working with RDBMS, providing both synchronous and asynchronous (the name begins with a) methods. DatabaseManager uses a singleton path, so it can be easily substituted in tests.

Example of use:

from app.core.config import DATABASE_URL, DEBUG, DEV_STATUS
from fastapi_accelerator.dbsession import MainDatabaseManager

DatabaseManager = MainDatabaseManager(DATABASE_URL, echo=DEBUG, DEV_STATUS=DEV_STATUS)
  • General characteristics

    • DEV_STATUS – Development mode indicator. At DEV_STATUS=False blocks critical operations from being executed (create_all, drop_all, clear_all). This is a safety measure for the production environment.

  • Synchronous components

    • database_url – Address for connecting to a synchronous database.

    • engine – Mechanism of synchronous interaction with the database.

    • session – Synchronous session generator.

    • Base – Base class for data models.

    • Functionality:

      • get_session – DB session injector.

      • get_session_transaction – DB session injector with transaction support.

      • create_all – Initialization of all tables in the database.

      • drop_all – Deleting the entire database structure.

      • clear_all – Clearing table contents. Parameter exclude_tables_name Allows you to exclude certain tables from the cleaning process.

  • Asynchronous components

    • adatabase_url – Address for connecting to an asynchronous database.

    • aengine – Asynchronous mechanism for working with the database, including connection pooling.

    • asession – Asynchronous session generator.

    • Functionality:

      • aget_session – Asynchronous DB session injector.

      • aget_session_transaction – Asynchronous DB session injector with transaction support.

OrmAsync

This class optimizes asynchronous interaction with the database:

  • get – Extraction of an object according to specified criteria.

  • get_list – Getting a set of objects on request. (With the possibility of deep selection)

  • update – Modification of objects according to request.

  • delete – Deleting objects according to specified parameters.

  • get_item – Extracting an object by primary key. (With the possibility of deep selection)

  • create_item – Creation of a new object. (With the possibility of cascading creation)

  • update_item – Updating an object by primary key. (With the possibility of cascading update)

  • delete_item – Deleting an object by primary key. (With the possibility of cascading deletion)

Deep selection/cascade operations are the ability to work with related data.
Activated by parameter deep=True

Examples:

  • get_list, get_item – Return objects with all associated data, ready for use in Pydantic

  • create_item – Creates records in related tables

  • update_item – Updates data in related tables

  • delete_item – Deletes records from related tables

ViewSet

ViewSet allows you to quickly create CRUD operations for models. Here is an example of usage:

from fastapi_accelerator.viewset import AppOrm, FullViewSet
from fastapi import APIRouter, Depends, Query

from app.api.v1.schemas.timemeasurement import TaskExecution
from app.models.timemeasurement import TaskExecution as TaskExecutionDb

router = APIRouter(prefix="/api/v1")

class FileViewSet(FullViewSet):
    """
    Представление для работы с файлами
    """

    # Модель БД
    db_model = TaskExecutionDb
    # Модель Схемы
    pydantic_model = TaskExecution

    '''
    # Кеширование
    cache_class = redis_client
    cache_ttl = timedelta(minutes=10)

    # Пагинация
    paginator_class = DefaultPaginator

    # Включить поддержку вложенных схем pydantic
    # это значит что будет происходить рекурсивное
    # создание, обновление, удаление связанных записей
    deep_schema = True

    # Включить защиту через JWT
    dependencies = [Depends(jwt_auth)]

    # Вы можете также переопределять методы:

    async def db_update(
        self, item_id: str | int | UUID, item: type[BaseModel], aorm: OrmAsync
    ) -> object:
        """Переопределение метода db_update"""
        return await super().db_update(item_id, item, aorm)

    def list(self):
        """Переопределение метода list"""

        @self.router.get(f"{self.prefix}", tags=self.tags)
        async def get_list_items(
            skip: int = Query(0),
            limit: int = Query(100),
            aorm: OrmAsync = Depends(AppOrm.aget_orm),
        ) -> List[self.pydantic_model]:
            return await aorm.get_list(
                self.db_model,
                select(self.db_model).offset(skip).limit(limit),
                deep=self.deep_schema,
            )
        return get_list_items
    '''

router.views = [
    FileViewSet().as_view(router, prefix="/file"),
]

JWT authentication

To secure API endpoints, we use JWT authentication:

from fastapi_accelerator.auth_jwt import BaseAuthJWT

class AuthJWT(BaseAuthJWT):
    def check_auth(username: str, password: str) -> bool:
        """Проверка введенного логина и пароля."""
        return username == "admin" and password == "admin"

AuthJWT.mount_auth(app)

Example of API method protection:

from fastapi_accelerator.auth_jwt import jwt_auth

@app.get("/check_protected", summary="Проверить аутентификацию по JWT")
async def protected_route(jwt: dict = Depends(jwt_auth)):
    return {"message": "This is a protected route", "user": jwt}

Admin panel

For convenient data management, we have integrated Flask-Admin:

from flask import Flask

from app.core.config import ADMIN_PASSWORD, ADMIN_USERNAME, SECRET_KEY
from app.db.base import DatabaseManager
from app.models import File, User
from fastapi_accelerator.pattern_flask_admin import base_pattern

app = Flask(__name__)

admin = base_pattern(
    app,
    SECRET_KEY,
    ADMIN_PASSWORD,
    ADMIN_USERNAME,
    # > Модели которые нужны в админ панели
    models=[User, File],
    database_manager=DatabaseManager,
)

if __name__ == "__main__":
    app.run(
        host="0.0.0.0",
        port=8001,
        debug=True,
    )

Testing

One of the key features of our toolkit is a powerful system for writing and running tests. It includes:

  1. Fixtures for working with a test database and API client.

  2. Decorators for authentication and fixture application.

  3. Context manager for tracking SQL queries.

  4. Utilities for checking JSON responses.

  5. Testing through classes.

Example of test function:

from typing import Callable, NamedTuple

from fastapi.testclient import TestClient

from app.fixture.items_v1 import export_fixture_file
from fastapi_accelerator.db.dbsession import MainDatabaseManager
from fastapi_accelerator.testutils import apply_fixture_db, client_auth_jwt, track_queries, check_response_json


# Аутентифицировать тестового клиента
@client_auth_jwt(username="test")
# Создать тестовые данных из функции с фикстурами
@apply_fixture_db(export_fixture_file)
def test_имя(
    client: TestClient,               # Тестовый клиент для API запросов
    url_path_for: Callable,           # Функция для получения url по имени функции обработчика
    db_manager: MainDatabaseManager,  # Менеджер тестовой БД
    fixtures: NamedTuple,             # Хранит созданные данные из фикстур
):
    # Проверка количество выполняемых SQL команд
    with track_queries(db_manager, expected_count=3):
        # Запрос в API
        response = client.get(url_path_for("ИмяФункции"))
    # Проверка JSON API ответа
    check_response_json(
        response,
        200,
        {
            "id": fixtures.Имя.id,
        },
    )

Example of class test:

from typing import Callable, NamedTuple

from fastapi.testclient import TestClient

from app.fixture.items_v1 import export_fixture_file
from fastapi_accelerator.db.dbsession import MainDatabaseManager
from fastapi_accelerator.testutils import apply_fixture_db
from fastapi_accelerator.testutils.fixture_auth import client_auth_jwt
from fastapi_accelerator.testutils.fixture_db.trace_sql import track_queries
from fastapi_accelerator.testutils.utils import BaseAuthJwtPytest, check_response_json

BASE_URL_V1 = "/api/v1/"

class TestИмя(BaseAuthJwtPytest):

    # Создать тестовые данных из функции с фикстурами
    @apply_fixture_db(export_fixture_file)
    def setUp(self, fixtures: NamedTuple):
        self.url = BASE_URL_V1 + "taskexecution"
        self.fixtures = fixtures # Хранит созданные данные из фикстур

    def test_имя(self, client: TestClient, db_manager: MainDatabaseManager):
        # Проверка количество выполняемых SQL команд
        with track_queries(db_manager, expected_count=3):
            # Запрос в API
            response = client.get(self.url)
        # Проверка JSON API ответа
        check_response_json(
            response,
            200,
            {
                "id": self.fixtures.Имя.id,
            },
        )

Comparison with existing solutions

Although there are several projects offering tools for developing and testing FastAPI applications, our solution stands out for its complexity and specialization:

  1. FastAPI-Utils: Provides development utilities, but is less focused on testing.

  2. FastAPI-SQLAlchemy: Integrates FastAPI with SQLAlchemy, including some testing utilities.

  3. FastAPI-Toolkit: Offers a set of tools, but is less specialized for testing tasks.

  4. freddie – In the archive on GitHub, only viewset

  5. fastapi_viewsets – Viewset only

  6. FastAPIwee – Less specialized in testing tasks.

Our solution is different in that:

  1. More specific to the tasks of testing FastAPI applications.

  2. Provides a wider range of tools for various aspects of testing.

  3. Includes unique features such as decorator @apply_fixture_db and context manager track_queries.

  4. Offers a comprehensive approach covering various aspects of developing and testing FastAPI applications.

Conclusion

The presented toolkit significantly simplifies and accelerates development on FastAPI. It provides ready-made solutions for typical tasks, improves the project structure and facilitates testing. Using these tools will allow developers to focus on the business logic of the application, rather than on the technical details of implementation.

While there are other tools in the FastAPI ecosystem, our solution stands out for its completeness and specialization specifically for testing tasks. This makes it a valuable addition to the existing resources for FastAPI developers.

We continue to develop this toolkit and welcome feedback from the community. If you have ideas for improvement or find a bug, please create an issue in our GitHub repository.

Development plans

  • Ensure compatibility with the latest(previous) versions of FastAPI and related libraries

  • Significantly increase code coverage with tests

  • Optimize existing code to improve performance

  • Conduct load testing and optimize critical sections of code

  • Refactor using best practices and design patterns

  • Develop and add a similar option to work with WebSocket

  • Improve the mechanism for executing background tasks (background tasks)

  • Improve the implementation of periodic tasks such as those presented in fastapi-utils

  • Create several detailed example projects demonstrating different use cases

  • Expand the documentation by adding more practical guides and application recommendations

  • Explore integration opportunities with popular tools in the FastAPI ecosystem

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *