Sanic – быстрый асинхронный веб-фреймворк

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в Sanic

Sanic — это современный асинхронный веб-фреймворк для Python, разработанный для создания высокопроизводительных веб-приложений. Основанный на asyncio, он позволяет эффективно обрабатывать тысячи одновременных соединений благодаря поддержке async/await синтаксиса. Sanic идеально подходит для создания REST API, микросервисов, real-time приложений и бэкендов с высокой нагрузкой.

Ключевые особенности Sanic

Производительность и архитектура

Sanic построен с акцентом на скорость выполнения. Фреймворк использует asyncio для асинхронной обработки запросов, что позволяет достичь производительности, сравнимой с Node.js и Go. Встроенная поддержка async/await делает код более читаемым и эффективным.

Функциональные возможности

  • Полная поддержка async/await из коробки
  • Маршрутизация с параметрами и middleware
  • Встроенная поддержка WebSocket
  • Система шаблонов и работа со статическими файлами
  • Поддержка CORS, cookie и сессий
  • Простая и понятная структура проекта
  • Отличная масштабируемость для API-серверов

Установка и начальная настройка

Базовая установка

pip install sanic

Дополнительные зависимости

pip install sanic[ext]  # Расширения
pip install jinja2 aiofiles python-dotenv

Создание первого приложения

from sanic import Sanic
from sanic.response import text

app = Sanic("MyApp")

@app.get("/")
async def index(request):
    return text("Привет от Sanic")

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

Маршрутизация и обработка запросов

Базовые маршруты

@app.get("/")
async def get_handler(request):
    return text("GET запрос")

@app.post("/")
async def post_handler(request):
    return text("POST запрос")

@app.put("/")
async def put_handler(request):
    return text("PUT запрос")

@app.delete("/")
async def delete_handler(request):
    return text("DELETE запрос")

Асинхронные маршруты

import asyncio

@app.get("/async")
async def async_route(request):
    await asyncio.sleep(1)  # Имитация асинхронной операции
    return text("Асинхронный ответ")

Работа с параметрами и данными

Параметры маршрута

@app.get("/user/<user_id:int>")
async def get_user(request, user_id):
    return text(f"ID пользователя: {user_id}")

@app.get("/profile/<username:str>")
async def get_profile(request, username):
    return text(f"Профиль: {username}")

Query-параметры

@app.get("/search")
async def search(request):
    query = request.args.get("q", "ничего")
    limit = request.args.get("limit", "10")
    return text(f"Поиск: {query}, лимит: {limit}")

Обработка JSON и форм

from sanic.response import json

@app.post("/api/data")
async def receive_data(request):
    data = request.json
    return json({"received": data})

@app.post("/form")
async def handle_form(request):
    username = request.form.get("username")
    email = request.form.get("email")
    return json({"username": username, "email": email})

Типы ответов

Различные форматы ответов

from sanic.response import text, json, html, redirect, file

@app.get("/text")
async def text_response(request):
    return text("Простой текст")

@app.get("/json")
async def json_response(request):
    return json({"message": "JSON ответ"})

@app.get("/html")
async def html_response(request):
    return html("<h1>HTML страница</h1>")

@app.get("/redirect")
async def redirect_response(request):
    return redirect("/new-page")

@app.get("/file")
async def file_response(request):
    return await file("path/to/file.pdf")

Обработка ошибок

Встроенные исключения

from sanic.exceptions import NotFound, SanicException, Unauthorized

@app.exception(NotFound)
async def handle_404(request, exception):
    return json({"error": "Страница не найдена"}, status=404)

@app.exception(Exception)
async def handle_generic_error(request, exception):
    return json({"error": "Внутренняя ошибка сервера"}, status=500)

Кастомные исключения

class CustomException(SanicException):
    status_code = 400
    message = "Кастомная ошибка"

@app.exception(CustomException)
async def handle_custom_error(request, exception):
    return json({"error": exception.message}, status=exception.status_code)

Middleware и жизненный цикл

Request и Response Middleware

@app.middleware("request")
async def before_request(request):
    request.ctx.start_time = time.time()
    print(f"Запрос к {request.path}")

@app.middleware("response")
async def after_response(request, response):
    response.headers["X-Framework"] = "Sanic"
    response.headers["X-Process-Time"] = str(time.time() - request.ctx.start_time)

Lifecycle хуки

@app.before_server_start
async def setup_db(app, loop):
    print("Подключение к базе данных")

@app.after_server_stop
async def close_db(app, loop):
    print("Закрытие соединения с базой данных")

Работа с cookie и сессиями

Cookie

@app.get("/set-cookie")
async def set_cookie(request):
    response = text("Cookie установлен")
    response.cookies["session_id"] = "123456"
    response.cookies["session_id"]["httponly"] = True
    response.cookies["session_id"]["secure"] = True
    return response

@app.get("/get-cookie")
async def get_cookie(request):
    session_id = request.cookies.get("session_id", "не установлен")
    return text(f"Session ID: {session_id}")

Работа с заголовками

@app.get("/headers")
async def handle_headers(request):
    user_agent = request.headers.get("User-Agent")
    auth_header = request.headers.get("Authorization")
    
    response = json({"user_agent": user_agent})
    response.headers["Custom-Header"] = "Sanic-Value"
    return response

Загрузка файлов

Обработка файлов

import os

@app.post("/upload")
async def upload_file(request):
    file = request.files.get("file")
    if file:
        upload_dir = "./uploads"
        os.makedirs(upload_dir, exist_ok=True)
        
        file_path = os.path.join(upload_dir, file.name)
        with open(file_path, "wb") as f:
            f.write(file.body)
        
        return json({"message": "Файл загружен", "filename": file.name})
    return json({"error": "Файл не найден"}, status=400)

Шаблоны и статические файлы

Интеграция с Jinja2

from sanic_ext import Extend
from jinja2 import Environment, FileSystemLoader

app = Sanic("TemplateApp")
Extend(app)

@app.get("/template")
async def render_template(request):
    return await app.ext.render("index.html", context={"name": "Sanic"})

Статические файлы

app.static("/static", "./static")
app.static("/media", "./media", name="media")

WebSocket поддержка

Базовый WebSocket

@app.websocket("/ws")
async def websocket_handler(request, ws):
    while True:
        try:
            message = await ws.recv()
            await ws.send(f"Эхо: {message}")
        except:
            break

WebSocket с аутентификацией

@app.websocket("/ws/auth")
async def auth_websocket(request, ws):
    token = request.headers.get("Authorization")
    if not token:
        await ws.close(code=1008, reason="Unauthorized")
        return
    
    while True:
        message = await ws.recv()
        await ws.send(f"Authenticated echo: {message}")

Интеграция с базами данных

Использование с SQLAlchemy

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

engine = create_async_engine("sqlite+aiosqlite:///./database.db")
SessionLocal = sessionmaker(bind=engine, class_=AsyncSession)

@app.middleware("request")
async def inject_session(request):
    request.ctx.session = SessionLocal()

@app.middleware("response")
async def close_session(request, response):
    await request.ctx.session.close()

Работа с async/await ORM

@app.get("/users")
async def get_users(request):
    async with request.ctx.session as session:
        # Пример работы с ORM
        users = await session.execute("SELECT * FROM users")
        return json([dict(user) for user in users])

Структура проекта

Рекомендуемая организация

project/
├── app.py              # Основное приложение
├── config.py           # Конфигурация
├── requirements.txt    # Зависимости
├── routes/            # Маршруты
│   ├── __init__.py
│   ├── api.py
│   └── auth.py
├── models/            # Модели данных
│   ├── __init__.py
│   └── user.py
├── middleware/        # Middleware
│   ├── __init__.py
│   └── auth.py
├── templates/         # Шаблоны
│   └── index.html
├── static/           # Статические файлы
│   ├── css/
│   └── js/
└── tests/            # Тесты
    ├── __init__.py
    └── test_app.py

Blueprints для модульности

Создание Blueprint

from sanic import Blueprint

# routes/users.py
users_bp = Blueprint("users", url_prefix="/users")

@users_bp.get("/")
async def get_users(request):
    return json({"users": []})

@users_bp.get("/<user_id:int>")
async def get_user(request, user_id):
    return json({"user_id": user_id})

# app.py
from routes.users import users_bp
app.blueprint(users_bp)

Тестирование

Написание тестов

import pytest
from app import app

@pytest.mark.asyncio
async def test_index():
    request, response = await app.asgi_client.get("/")
    assert response.status == 200
    assert "Привет от Sanic" in response.text

@pytest.mark.asyncio
async def test_json_endpoint():
    request, response = await app.asgi_client.get("/api/data")
    assert response.status == 200
    assert response.json["status"] == "ok"

Тестирование с фикстурами

@pytest.fixture
def test_client():
    return app.asgi_client

@pytest.mark.asyncio
async def test_with_fixture(test_client):
    request, response = await test_client.post("/api/users", json={"name": "Test"})
    assert response.status == 201

Развертывание и продакшн

Настройки для продакшн

# config.py
class ProductionConfig:
    DEBUG = False
    ACCESS_LOG = False
    WORKERS = 4
    HOST = "0.0.0.0"
    PORT = 8000

# app.py
from config import ProductionConfig

if __name__ == "__main__":
    app.run(
        host=ProductionConfig.HOST,
        port=ProductionConfig.PORT,
        workers=ProductionConfig.WORKERS,
        debug=ProductionConfig.DEBUG,
        access_log=ProductionConfig.ACCESS_LOG
    )

Запуск через командную строку

# Базовый запуск
sanic app.app --host=0.0.0.0 --port=8000

# Продакшн с воркерами
sanic app.app --host=0.0.0.0 --port=8000 --workers=4

# С дополнительными опциями
sanic app.app --workers=4 --access-log --debug

Использование с Gunicorn

pip install gunicorn
gunicorn app:app --bind 0.0.0.0:8000 --worker-class sanic.worker.GunicornWorker

Полная таблица методов и функций Sanic

Категория Метод/Функция Описание Пример использования
Создание приложения Sanic(name) Создает экземпляр приложения app = Sanic("MyApp")
Маршрутизация @app.get(path) GET маршрут @app.get("/users")
  @app.post(path) POST маршрут @app.post("/users")
  @app.put(path) PUT маршрут @app.put("/users/<id>")
  @app.delete(path) DELETE маршрут @app.delete("/users/<id>")
  @app.patch(path) PATCH маршрут @app.patch("/users/<id>")
  @app.head(path) HEAD маршрут @app.head("/users")
  @app.options(path) OPTIONS маршрут @app.options("/users")
  @app.route(path, methods) Универсальный маршрут @app.route("/api", methods=["GET", "POST"])
Запуск приложения app.run() Запуск сервера app.run(host="0.0.0.0", port=8000)
  app.create_server() Создание сервера server = app.create_server()
Объект запроса request.args Query параметры request.args.get("param")
  request.json JSON данные data = request.json
  request.form Данные формы request.form.get("field")
  request.files Загруженные файлы request.files.get("file")
  request.headers HTTP заголовки request.headers.get("Authorization")
  request.cookies Cookies request.cookies.get("session")
  request.path Путь запроса path = request.path
  request.method HTTP метод method = request.method
  request.ip IP адрес клиента ip = request.ip
  request.url Полный URL url = request.url
  request.ctx Контекст запроса request.ctx.user = user
Ответы text(content) Текстовый ответ return text("Hello")
  json(data) JSON ответ return json({"key": "value"})
  html(content) HTML ответ return html("<h1>Title</h1>")
  redirect(url) Перенаправление return redirect("/new-page")
  file(path) Файловый ответ return await file("./image.jpg")
  stream(func) Потоковый ответ return stream(stream_func)
  response.cookies Установка cookies response.cookies["key"] = "value"
  response.headers Установка заголовков response.headers["X-Custom"] = "value"
Middleware @app.middleware("request") Middleware для запросов @app.middleware("request")
  @app.middleware("response") Middleware для ответов @app.middleware("response")
Обработка ошибок @app.exception(Exception) Обработчик исключений @app.exception(NotFound)
  SanicException Базовое исключение raise SanicException("Error")
  NotFound Ошибка 404 raise NotFound("Not found")
  Unauthorized Ошибка 401 raise Unauthorized("Auth required")
  Forbidden Ошибка 403 raise Forbidden("Access denied")
Lifecycle хуки @app.before_server_start До запуска сервера @app.before_server_start
  @app.after_server_start После запуска сервера @app.after_server_start
  @app.before_server_stop До остановки сервера @app.before_server_stop
  @app.after_server_stop После остановки сервера @app.after_server_stop
WebSocket @app.websocket(path) WebSocket маршрут @app.websocket("/ws")
  ws.send(data) Отправка сообщения await ws.send("message")
  ws.recv() Получение сообщения message = await ws.recv()
  ws.close() Закрытие соединения await ws.close()
Blueprints Blueprint(name) Создание blueprint bp = Blueprint("api")
  app.blueprint(bp) Регистрация blueprint app.blueprint(api_bp)
Статические файлы app.static(uri, file_or_dir) Статические файлы app.static("/static", "./static")
Конфигурация app.config Конфигурация приложения app.config.DEBUG = True
  app.config.from_object() Загрузка из объекта app.config.from_object(Config)
Listeners @app.listener("before_server_start") Слушатель событий @app.listener("before_server_start")
Задачи app.add_task(coro) Добавление фоновой задачи app.add_task(background_task())
Тестирование app.test_client Тестовый клиент client = app.test_client
  app.asgi_client ASGI клиент await app.asgi_client.get("/")

Расширенные возможности

Кастомные декораторы

from functools import wraps

def require_auth(f):
    @wraps(f)
    async def decorated_function(request, *args, **kwargs):
        auth = request.headers.get("Authorization")
        if not auth:
            return json({"error": "Unauthorized"}, status=401)
        return await f(request, *args, **kwargs)
    return decorated_function

@app.get("/protected")
@require_auth
async def protected_route(request):
    return json({"message": "Protected content"})

Валидация данных

from cerberus import Validator

@app.post("/api/users")
async def create_user(request):
    schema = {
        'name': {'type': 'string', 'required': True},
        'email': {'type': 'string', 'required': True, 'regex': r'^[^@]+@[^@]+\.[^@]+$'},
        'age': {'type': 'integer', 'min': 0, 'max': 150}
    }
    
    v = Validator(schema)
    if not v.validate(request.json):
        return json({"errors": v.errors}, status=400)
    
    # Обработка валидных данных
    return json({"message": "User created"}, status=201)

Практические примеры применения

RESTful API

users_data = []

@app.get("/api/users")
async def get_users(request):
    return json(users_data)

@app.post("/api/users")
async def create_user(request):
    user = request.json
    user['id'] = len(users_data) + 1
    users_data.append(user)
    return json(user, status=201)

@app.get("/api/users/<user_id:int>")
async def get_user(request, user_id):
    user = next((u for u in users_data if u['id'] == user_id), None)
    if not user:
        return json({"error": "User not found"}, status=404)
    return json(user)

WebSocket чат

connected_clients = set()

@app.websocket("/chat")
async def chat_handler(request, ws):
    connected_clients.add(ws)
    try:
        async for message in ws:
            # Отправляем сообщение всем подключенным клиентам
            for client in connected_clients.copy():
                try:
                    await client.send(message)
                except:
                    connected_clients.remove(client)
    finally:
        connected_clients.discard(ws)

Оптимизация производительности

Настройки для высокой нагрузки

# Оптимизированная конфигурация
app.config.update({
    "KEEP_ALIVE_TIMEOUT": 30,
    "REQUEST_TIMEOUT": 60,
    "RESPONSE_TIMEOUT": 60,
    "REQUEST_MAX_SIZE": 100000000,  # 100MB
    "ACCESS_LOG": False,
    "AUTO_RELOAD": False,
})

Использование connection pooling

import aioredis
from aiohttp import ClientSession

@app.before_server_start
async def setup_connections(app, loop):
    # Redis connection pool
    app.ctx.redis = await aioredis.create_redis_pool(
        'redis://localhost',
        maxsize=10
    )
    
    # HTTP client session
    app.ctx.http_session = ClientSession()

@app.after_server_stop
async def cleanup_connections(app, loop):
    app.ctx.redis.close()
    await app.ctx.redis.wait_closed()
    await app.ctx.http_session.close()

Безопасность

CORS настройки

from sanic_cors import CORS

CORS(app, resources={
    r"/api/*": {
        "origins": ["http://localhost:3000"],
        "methods": ["GET", "POST", "PUT", "DELETE"],
        "allow_headers": ["Content-Type", "Authorization"]
    }
})

Ограничение частоты запросов

from collections import defaultdict
import time

request_counts = defaultdict(list)

@app.middleware("request")
async def rate_limit(request):
    client_ip = request.ip
    now = time.time()
    
    # Очистка старых запросов
    request_counts[client_ip] = [
        req_time for req_time in request_counts[client_ip]
        if now - req_time < 60  # За последнюю минуту
    ]
    
    # Проверка лимита
    if len(request_counts[client_ip]) >= 100:  # 100 запросов в минуту
        return json({"error": "Rate limit exceeded"}, status=429)
    
    request_counts[client_ip].append(now)

Часто задаваемые вопросы

Что такое Sanic и для чего он используется?

Sanic — это высокопроизводительный асинхронный веб-фреймворк для Python, использующий async/await синтаксис. Он предназначен для создания быстрых API, веб-сервисов и микросервисов с возможностью обработки большого количества одновременных соединений.

Чем Sanic отличается от Flask и Django?

В отличие от Flask и Django, Sanic полностью асинхронный, что обеспечивает значительно более высокую производительность при обработке множественных запросов. Он также имеет встроенную поддержку WebSocket и более современный подход к разработке API.

Можно ли использовать Sanic в продакшн среде?

Да, Sanic подходит для продакшн использования. Многие компании успешно используют его для высоконагруженных приложений. Для продакшн рекомендуется использовать несколько воркеров и настроить соответствующие параметры производительности.

Поддерживает ли Sanic работу с базами данных?

Sanic отлично работает с асинхронными ORM и драйверами баз данных, такими как SQLAlchemy (с asyncio), asyncpg для PostgreSQL, aiomysql для MySQL и другими async-совместимыми библиотеками.

Как обеспечить безопасность в Sanic приложениях?

Sanic поддерживает стандартные методы безопасности: HTTPS, CORS, аутентификацию через JWT, валидацию входящих данных, ограничение частоты запросов и использование middleware для проверки прав доступа.

Какие инструменты тестирования доступны для Sanic?

Sanic предоставляет встроенный тестовый клиент, поддерживает pytest для асинхронных тестов и имеет отличную совместимость с популярными библиотеками для тестирования Python приложений.

Новости