Решил в этой статье пройтись обзорно по основным функциям.
- Преимущества фреймворка
- Docker образ с библиотеками и vscode-server
- Первое приложение
- Валидация данных
- Асинхронное выполнение задачи
- Куки и заголовки
- Авторизация
- Обработка ошибок
- Тестирование
- Обработка событий
Преимущества фреймворка
- легкий старт за счет декораторов- встроенный веб сервер
- async io из коробки для асинхронной работы с http
- валидация данных на основе схемы
- автоматическая swagger дока
- авторизация и прочее
Docker образ с библиотеками и vscode-server
Ниже образ, который я использовал для тестов. Он включает в себя python 3.12, fastapi, request и сервер vscode для работы с образом снаружи.
FROM python:3.12-slim
SHELL ["/bin/bash", "-c"]
RUN python3.12 -m venv ~/.api && \
source ~/.api/bin/activate && \
pip install fastapi[all] && \
pip install import requests && \
mkdir -p /opt/fastapi && \
apt-get -y update && \
apt-get -y install curl
# install VS Code (code-server)
RUN curl -fsSL https://code-server.dev/install.sh | sh
# install VS Code extensions
RUN code-server --install-extension redhat.vscode-yaml \
--install-extension ms-python.python
ENTRYPOINT ["/bin/bash"]
Сборка:
docker build . -t fastapiМаппинг папки с сервером vscode на локальную папку, чтобы каждый раз его заново не ставить:
docker run -it \
-v /home/pihel/Documents/fastapi/vscode-server:/root/.vscode-server \
-v /home/pihel/Documents/fastapi/code:/opt/fastapi \
--entrypoint /bin/bash \
--rm \
fastapi
Первое приложение
Для быстрого старта достаточно минимума действий:
from fastapi import FastAPI
app = FastAPI()
@app.post('/calculate')
async def calculate(num1: int, num2: int):
return {f'sum of numbers {num1} and {num2} is ': f'{num1+num2}'}
#виды параметров можно совмещать
@app.get("/users/{user_id}")
def read_user(user_id: int, is_admin: bool = False):
return {"user_id": user_id, "is_admin": is_admin}
Запуск приложения
uvicorn main:app --reloadПосле этого приложение можно открыть по адерсу: http://127.0.0.1:8000/
Автоматически генерируется swagger дока по адресу http://127.0.0.1:8000/docs
Валидация данных
Кроме валидации происходит преобразование к датаклассу User:
from pydantic import BaseModel
class User(BaseModel):
username: str
message: str
@app.post("/")
async def root(user: User):
Передача json с данными:
{
"username": "Vasya",
"message": "I am BATMAN"
}
Для выходных данных тоже можно задать выходной дата тип
@app.post('/add_user', response_model=User) # тут указали модель (формат) ответа
async def add_user(user: User): # собственно тут проверяем входные данные на соответствие модели
fake_db.append({"username": user.username, "user_info": user.user_info}) # тут добавили юзера в фейковую БД
return user
Параметры могут передаваться в части пути, так и обычными параметрами запроса:
@app.get('/{user_id}') # тут объявили параметр пути
async def search_user_by_id(user_id: int): # тут указали его тип данных
# какая-то логика работы поиска
return {"вы просили найти юзера с id": user_id}
Асинхронное выполнение задачи
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
def write_notification(email: str, message=""):
with open("log.txt", mode="w") as email_file:
content = f"notification for {email}: {message}"
email_file.write(content)
@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
background_tasks.add_task(write_notification, email, message="some notification")
return {"message": "Notification sent in the background"}
Куки и заголовки
Куки:
@app.get("/")
def root(last_visit = Cookie()):
return {"last visit": last_visit}
Заголовки:
from fastapi import Header
@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
return {"User-Agent": user_agent}
Авторизация
def authenticate_user(credentials: HTTPBasicCredentials = Depends(security)):
user = get_user_from_db(credentials.username)
if user is None or user.password != credentials.password:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
return user
@app.get("/protected_resource/")
def get_protected_resource(user: User = Depends(authenticate_user)):
return {"message": "You have access to the protected resource!", "user_info": user}
Обработка ошибок
Виды своих ошибок будут отображены в swagger доке
# не изменяли
class CustomException(HTTPException):
def __init__(self, detail: str, status_code: int = 400):
super().__init__(status_code=status_code, detail=detail)
# Обработчик ошибок (error handler) для класса CustomException
@app.exception_handler(CustomException)
async def custom_exception_handler(request: Request, exc: CustomException):
return JSONResponse(
status_code=exc.status_code,
content={"error": exc.detail}
)
# не изменяли
@app.get("/items/{item_id}/")
async def read_item(item_id: int):
if item_id == 42:
raise CustomException(detail="Item not found", status_code=404)
return {"item_id": item_id}
Тестирование
Тестовый клиент для обращения к апи:
from fastapi.testclient import TestClient
from my_app import app
client = TestClient(app)
def test_calculate_sum():
response = client.get("/sum/?a=5&b=10")
assert response.status_code == 200
assert response.json() == {"result": 15}
Сами апи вызовы нужно мокать
Обработка событий
@app.middleware("http")
async def my_middleware(request: Request, call_next):
print('Мидлвэр начал работу')
response = await call_next(request)
print('Мидлвэр получил обратно управление')
return response
@app.get("/")
def index():
print('привет из основного обработчика пути')
return {"message": "Hello, world!"}
Статья не претендует на полноту, это лишь обзор возможностей фреймворка для понимания его возможностей.
Комментариев нет:
Отправить комментарий