Создание REST API с помощью FastAPI и SQLite
Основной подход: создание веб-API на FastAPI
Для разработки современного REST API на Python часто выбирают фреймворк FastAPI. Он поддерживает асинхронность, автоматическую документацию и валидацию данных. Рассмотрим создание простого API для управления задачами (TODO).
Шаг 1. Установка зависимостей
Необходимо установить FastAPI, сервер Uvicorn и SQLAlchemy для работы с базой данных:
pip install fastapi uvicorn sqlalchemyPython пример разработки (пример разработки на python)
Шаг 2. Создание структуры проекта
Создадим файл main.py со следующим содержимым:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# Настройка базы данных SQLite
SQLALCHEMY_DATABASE_URL = 'sqlite:///./todos.db'
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={'check_same_thread': False})
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
Base = declarative_base()
# Определение модели Task
class Task(Base):
__tablename__ = 'tasks'
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
completed = Column(Boolean, default=False)
Base.metadata.create_all(bind=engine)
# Pydantic модель для запросов и ответов
class TaskCreate(BaseModel):
title: str
completed: bool = False
class TaskResponse(BaseModel):
id: int
title: str
completed: bool
app = FastAPI()
# Зависимость для получения сессии БД
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post('/tasks/', response_model=TaskResponse)
def create_task(task: TaskCreate):
db = next(get_db())
db_task = Task(title=task.title, completed=task.completed)
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
@app.get('/tasks/', response_model=list[TaskResponse])
def read_tasks():
db = next(get_db())
tasks = db.query(Task).all()
return tasks
@app.get('/tasks/{task_id}', response_model=TaskResponse)
def read_task(task_id: int):
db = next(get_db())
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail='Task not found')
return task
@app.put('/tasks/{task_id}', response_model=TaskResponse)
def update_task(task_id: int, task: TaskCreate):
db = next(get_db())
db_task = db.query(Task).filter(Task.id == task_id).first()
if not db_task:
raise HTTPException(status_code=404, detail='Task not found')
db_task.title = task.title
db_task.completed = task.completed
db.commit()
db.refresh(db_task)
return db_task
@app.delete('/tasks/{task_id}')
def delete_task(task_id: int):
db = next(get_db())
db_task = db.query(Task).filter(Task.id == task_id).first()
if not db_task:
raise HTTPException(status_code=404, detail='Task not found')
db.delete(db_task)
db.commit()
return {'ok': True}
Шаг 3. Запуск сервера
Выполните команду в терминале:
uvicorn main:app --reload
Сервер запустится на http://127.0.0.1:8000. Документация доступна по адресу /docs.
Типичные ошибки и их решения:
- Ошибка импорта sqlalchemy - не установлен пакет. Решение: выполнить pip install sqlalchemy.
- Ошибка No module named 'pydantic' - требуется установка pydantic, обычно входит в состав FastAPI, но если нет, установить отдельно.
- Проблемы с потоками SQLite: в строке подключения указан параметр check_same_thread=False. Без него FastAPI может выдавать ошибку при работе с несколькими запросами.
- Ошибка 404 при обращении к несуществующему id - код предусматривает обработку, но если забыть, будет исключение.
Как создать аналогичное API с помощью Flask?
Flask - более простой фреймворк. Для REST API используют расширение Flask-RESTful или просто декораторы. Пример:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todos.db'
db = SQLAlchemy(app)
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100))
completed = db.Column(db.Boolean, default=False)
with app.app_context():
db.create_all()
@app.route('/tasks', methods=['POST'])
def create_task():
data = request.get_json()
task = Task(title=data['title'], completed=data.get('completed', False))
db.session.add(task)
db.session.commit()
return jsonify({'id': task.id, 'title': task.title, 'completed': task.completed}), 201
@app.route('/tasks', methods=['GET'])
def get_tasks():
tasks = Task.query.all()
return jsonify([{'id': t.id, 'title': t.title, 'completed': t.completed} for t in tasks])
# ... другие маршруты
Проблемы Flask подхода:
- Необходимость ручной валидации данных (отсутствие Pydantic).
- Меньшая производительность по сравнению с FastAPI.
- Отсутствие автоматической документации.
Какие возможности даёт Django REST Framework?
Для крупных проектов выбирают Django с DRF. Пример сериализатора и вьюсета:
# serializers.py
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
# views.py
from rest_framework import viewsets
from .models import Task
from .serializers import TaskSerializer
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
Особенности и сложности:
- Большой объем кода и конфигурации.
- Высокий порог входа.
- Мощная админка и ORM.
Как обойтись без ORM, используя чистый SQL?
Для простых проектов можно напрямую работать с sqlite3. Пример:
import sqlite3
from fastapi import FastAPI, HTTPException
app = FastAPI()
def get_db_connection():
conn = sqlite3.connect('todos.db')
conn.row_factory = sqlite3.Row
return conn
@app.on_event('startup')
def startup():
conn = get_db_connection()
conn.execute('CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY, title TEXT, completed INTEGER)')
conn.commit()
conn.close()
@app.get('/tasks')
def read_tasks():
conn = get_db_connection()
tasks = conn.execute('SELECT * FROM tasks').fetchall()
conn.close()
return [dict(t) for t in tasks]
Недостатки чистого SQL:
- Ручное управление подключениями, риск утечки ресурсов.
- Отсутствие автоматического маппинга объектов.
- Необходимость писать SQL-запросы вручную.
Как подключить MongoDB вместо SQLite?
Для NoSQL базы данных используется pymongo. Пример вставки и чтения:
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client.tododb
tasks_collection = db.tasks
@app.post('/tasks')
def create_task(task: TaskCreate):
result = tasks_collection.insert_one(task.dict())
new_task = tasks_collection.find_one({'_id': result.inserted_id})
return {'id': str(new_task['_id']), 'title': new_task['title'], 'completed': new_task['completed']}
Проблемы перехода на MongoDB:
- Отсутствие схемы данных, сложнее валидация.
- Необходимость установки и настройки MongoDB сервера.
- Отличия в синтаксисе запросов.
Расширенные примеры разработки на Python
1. Асинхронные эндпоинты в FastAPI
FastAPI поддерживает асинхронные функции. Пример асинхронного получения данных из базы через SQLAlchemy (с использованием async SQLAlchemy):
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from sqlalchemy import select
DATABASE_URL = 'sqlite+aiosqlite:///./test.db'
engine = create_async_engine(DATABASE_URL, echo=True)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_async_db():
async with async_session() as session:
yield session
@app.get('/async-tasks')
async def read_async_tasks(db: AsyncSession = Depends(get_async_db)):
result = await db.execute(select(Task))
tasks = result.scalars().all()
return [{'id': t.id, 'title': t.title, 'completed': t.completed} for t in tasks]
Результат выполнения GET запроса к /async-tasks:
[{'id': 1, 'title': 'Example', 'completed': False}]
2. Обработка ошибок с помощью exception handlers
Можно переопределить стандартные обработчики ошибок для возврата единообразного ответа:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class CustomException(Exception):
def __init__(self, message: str, status_code: int = 400):
self.message = message
self.status_code = status_code
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=exc.status_code,
content={'detail': exc.message, 'status': 'error'}
)
@app.get('/raise-error')
def raise_error():
raise CustomException('Произошла ошибка', status_code=422)
Ответ при обращении к /raise-error:
{'detail': 'Произошла ошибка', 'status': 'error'}
3. Тестирование API с pytest и httpx
Для автоматического тестирования удобно использовать клиент httpx вместе с TestClient от FastAPI:
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_create_task():
response = client.post('/tasks/', json={'title': 'Test'})
assert response.status_code == 200
data = response.json()
assert data['title'] == 'Test'
assert data['completed'] == False
def test_get_tasks():
response = client.get('/tasks/')
assert response.status_code == 200
assert isinstance(response.json(), list)
Результат запуска тестов:
============================= test session starts ============================== collected 2 items test_main.py .. [100%] ============================== 2 passed in 0.25s ===============================