Перейти к содержанию

Esmerald

Esmerald

🚀 Масштабируемость, производительность, легкость в изучении и написании кода, подходит для любого приложения. 🚀

Test Suite Package version Supported Python versions


Документация: https://esmerald.dev 📚

Исходный код: https://github.com/dymmond/esmerald


Esmerald — это современный, мощный, гибкий и высокопроизводительный веб-фреймворк, созданный для построения не только API, но и полноценных масштабируемых приложений — от самых малых до уровня крупных компаний.

Esmerald разрабатывался для Python 3.9+ и использует стандартные подсказки типов (type hints) Python, основан на широко известном Lilya и Pydantic/msgspec.

Success

Официально поддерживается только последняя выпущенная версия.

Мотивация

Существуют отличные фреймворки такие, как FastAPI, Flama, Flask, Django и другие, решающие большинство повседневных задач для 99% приложений, но оставляющие тот 1%, который обычно связан со структурой и бизнес-логикой, без особых решений.

Esmerald черпает вдохновение в этих фреймворках и обладает всеми их известными возможностями, но также учитывает потребности бизнеса. Например, Starlite вдохновил на создание трансформеров и моделей Signature, что помогло интеграции с Pydantic. FastAPI вдохновил дизайн API, Django — систему разрешений, Flask — простоту, NestJS — контроллеры и многое другое.

Для качественной работы всегда требуется как драйвер, так и источник вдохновения.

Требования

  • Python 3.9+

Esmerald не был бы возможен без следующих двух компонентов:

Установка

$ pip install esmerald

Для работы в продакшене также потребуется ASGI сервер, рекомендуем Uvicorn, но выбор остается за вами.

$ pip install uvicorn

Поддержка встроенного планировщика::

$ pip install esmerald[schedulers]

Поддержка JWT, используемого внутри Esmerald::

$ pip install esmerald[jwt]

Для использования клиента тестирования Esmerald::

$ pip install esmerald[test]

Для использования оболочки Esmerald::

Подробнее здесь по теме в документации.

$ pip install esmerald[ipython] # default shell
$ pip install esmerald[ptpython] # ptpython shell

Начало проекта с использованием директив

Warning

Директивы рассчитаны на опытных пользователей, которые уже знакомы с Esmerald (или Python в целом), или если использование директив не вызывает затруднений. Если пока не чувствуете уверенности, продолжайте изучать документацию и знакомиться с Esmerald.

Чтобы начать Esmerald проект с простой предложенной структурой, выполните:

esmerald createproject <YOUR-PROJECT-NAME> --simple

Это создаст каркас проекта с некоторыми предопределенными файлами для простого запуска приложения Esmerald.

Также будет создан файл для тестов с использованием EsmeraldTestClient, так что выполните:

$ pip install esmerald[test]

Эту часть можно пропустить, если не хотите использовать EsmeraldTestClient.

Подробная информация об этой директиве и примерах ее использования.

Warning

Запуск этой директивы создает только каркас проекта, и для его запуска потребуются дополнительные данные. Этот каркас лишь предоставляет структуру файлов для начала работы, но не является обязательным.

Основные функции

  • Быстрый и эффективный: Благодаря Lilya и Pydantic/msgpec.
  • Быстрое развитие: Простота дизайна значительно сокращает время разработки.
  • Интуитивно понятный: Если знакомы с другими фреймворками, работать с Esmerald не составит труда.
  • Простота: Создан с учетом удобства и легкости в изучении.
  • Компактный: Благодаря встроенной поддержке ООП нет необходимости дублировать код. Поддержка SOLID.
  • Готовый к работе: Приложение запускается с готовым к продакшену кодом.
  • ООП и функциональный стиль: Проектируйте API любым удобным способом, поддержка ООП и функционального стиля.
  • Асинхронный и синхронный: Поддерживает как синхронный, так и асинхронный режимы.
  • Middleware: Применяйте middleware на уровне приложения или API.
  • Обработчики исключений: Применяйте обработчики на любом уровне.
  • Permissions: Применяйте правила и permissions для каждого API.
  • Interceptors: Перехватывайте запросы и добавляйте логику перед обработкой.
  • Плагины: Создавайте плагины для Esmerald и интегрируйте их в любое приложение, или опубликуйте свой пакет.
  • DAO и AsyncDAO: Избегайте вызовов базы данных напрямую из API, используйте бизнес-объекты.
  • Поддержка ORM: Поддержка [Edgy][_orm].
  • Поддержка ODM: Поддержка Mongoz.
  • APIView: Контроллеры в виде классов.
  • JSON сериализация/десериализация: Поддержка UJSON и ORJSON.
  • Lifespan: Поддержка lifespan Lilya.
  • Внедрение зависимостей: Как в любом хорошем фреймворке.
  • Планировщик: Поддержка задач в фоне.
  • Настройки: Поддержка системы настроек для чистоты кода.
  • msgspec — поддержка msgspec.

Отношение к Lilya и другим фреймворкам

Esmerald использует Lilya. Это решение обусловлено высокой производительностью и отсутствием проблем с маршрутизацией.

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

Быстрый старт

Пример как быстро начать работу с Esmerald. Для быстрого старта используйте uvicorn.

#!/usr/bin/env python
import uvicorn

from esmerald import Esmerald, Gateway, JSONResponse, Request, get


@get()
def welcome() -> JSONResponse:
    return JSONResponse({"message": "Welcome to Esmerald"})


@get()
def user(user: str) -> JSONResponse:
    return JSONResponse({"message": f"Welcome to Esmerald, {user}"})


@get()
def user_in_request(request: Request) -> JSONResponse:
    user = request.path_params["user"]
    return JSONResponse({"message": f"Welcome to Esmerald, {user}"})


app = Esmerald(
    routes=[
        Gateway("/esmerald", handler=welcome),
        Gateway("/esmerald/{user}", handler=user),
        Gateway("/esmerald/in-request/{user}", handler=user_in_request),
    ]
)


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

Затем вы можете получить доступ к endpoints.

Использование Esmerald в качестве декоратора

Чтобы быстро начать работу с Esmerald, вы также можете использовать его как декоратор. Вот как это сделать на примере с uvicorn.

#!/usr/bin/env python
import uvicorn

from esmerald import Esmerald, Gateway, JSONResponse, Request, get


app = Esmerald()


@app.get("/esmerald")
def welcome() -> JSONResponse:
    return JSONResponse({"message": "Welcome to Esmerald"})


@app.get("/esmerald/{user}")
def user(user: str) -> JSONResponse:
    return JSONResponse({"message": f"Welcome to Esmerald, {user}"})


@app.get("/esmerald/in-request/{user}")
def user_in_request(request: Request) -> JSONResponse:
    user = request.path_params["user"]
    return JSONResponse({"message": f"Welcome to Esmerald, {user}"})


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

Настройки

Как и в любом другом фреймворке, при запуске приложения множество настроек можно или необходимо передать главному объекту, что иногда выглядит сложно и неудобно для поддержки и восприятия.

Esmerald изначально учитывает настройки. Набор параметров по умолчанию можно изменить, используя собственный модуль настроек, но при этом вы также можете использовать классический подход, передавая все параметры непосредственно при создании экземпляра Esmerald.

Пример классического подхода:

from example import ApplicationObjectExample

# ExampleObject — это экземпляр другого приложения,
# и он служит только в качестве примера

app = ApplicationObjectExample(setting_one=..., setting_two=..., setting_three=...)

Вдохновленный замечательным Django и используя pydantic, Esmerald предоставляет объект по умолчанию, готовый к использованию сразу «из коробки».

Esmerald:

from esmerald import Esmerald


app = Esmerald()

И это все! Все настройки по умолчанию загружаются автоматически! Почему? Потому что приложение ищет переменную окружения ESMERALD_SETTINGS_MODULE для запуска, и если она не найдена, используются глобальные настройки приложения. Это просто, но можно ли переопределить их внутри объекта? Да, конечно.

from esmerald import Esmerald

app = Esmerald(app_name='My App', title='My title')

То же самое, что и классический подход.

Давайте поговорим о модуле настроек Esmerald.

Модуль настроек Esmerald

При запуске приложения система ищет переменную окружения ESMERALD_SETTINGS_MODULE. Если переменная не указана, система по умолчанию использует настройки EsmeraldSettings и запускается.

Пользовательские настройки

В наше время важно разделять настройки по окружениям и стандартных настроек Esmerald будет недостаточно для любого приложения.

Настройки соответствуют стандарту pydantic и, следовательно, совместимы с Esmerald. Система предоставляет несколько значений по умолчанию, которые можно использовать сразу, хотя это необязательно. Окружение по умолчанию — production.

from esmerald import EsmeraldSettings
from esmerald.conf.enums import EnvironmentType


class Development(EsmeraldSettings):
    app_name: str = 'My app in dev'
    environment: str = EnvironmentType.DEVELOPMENT

Загрузка настроек в ваше приложение Esmerald:

Предположим, ваше приложение Esmerald находится в файле src/app.py.

ESMERALD_SETTINGS_MODULE='myapp.settings.Development' python -m src.app.py
$env:ESMERALD_SETTINGS_MODULE="myapp.settings.Development"; python -m src.app.py

Gateway, WebSocketGateway и Include

Lilya предлагает классы Path для простых назначений путей, но это также очень ограничивает, если у вас есть что-то более сложное. Esmerald расширяет эту функциональность и добавляет немного 'стиля', улучшая её с помощью Gateway, WebSocketGateway и Include.

Эти специальные объекты позволяют происходить всей магии Esmerald.

Для классического, прямого подхода в одном файле:

src/app.py
from esmerald import Esmerald, Gateway, JSONResponse, Request, Websocket, WebSocketGateway, get, status


@get(status_code=status.HTTP_200_OK)
async def home() -> JSONResponse:
    return JSONResponse({
        "detail": "Hello world"
    })


@get()
async def another(request: Request) -> dict:
    return {
        "detail": "Another world!"
    }


@websocket(path="/{path_param:str}")
async def world_socket(socket: Websocket) -> None:
    await socket.accept()
    msg = await socket.receive_json()
    assert msg
    assert socket
    await socket.close()


app = Esmerald(routes=[
    Gateway(handler=home),
    Gateway(handler=another),
    WebSocketGateway(handler=world_socket),
])

Дизайн маршрутов

Хороший дизайн всегда приветствуется и Esmerald позволяет создавать сложные маршруты на любом уровне.

Обработчики (контроллеры)

src/myapp/accounts/controllers.py
from pydantic import BaseModel

from esmerald import (
    APIView,
    JSONResponse,
    Request,
    Response,
    WebSocket,
    get,
    post,
    put,
    status,
    websocket,
)


class Product(BaseModel):
    name: str
    sku: str
    price: float


@put("/product/{product_id}")
def update_product(product_id: int, data: Product) -> dict:
    return {"product_id": product_id, "product_name": data.name}


@get(status_code=status.HTTP_200_OK)
async def home() -> JSONResponse:
    return JSONResponse({"detail": "Hello world"})


@get()
async def another(request: Request) -> dict:
    return {"detail": "Another world!"}


@websocket(path="/{path_param:str}")
async def world_socket(socket: WebSocket) -> None:
    await socket.accept()
    msg = await socket.receive_json()
    assert msg
    assert socket
    await socket.close()


class World(APIView):
    @get(path="/{url}")
    async def home(self, request: Request, url: str) -> Response:
        return Response(f"URL: {url}")

    @post(path="/{url}", status_code=status.HTTP_201_CREATED)
    async def mars(self, request: Request, url: str) -> JSONResponse: ...

    @websocket(path="/{path_param:str}")
    async def pluto(self, socket: WebSocket) -> None:
        await socket.accept()
        msg = await socket.receive_json()
        assert msg
        assert socket
        await socket.close()

Если path не указан, по умолчанию используется /.

Gateways (urls)

myapp/accounts/urls.py
from esmerald import Gateway, WebSocketGateway
from .controllers import home, another, world_socket, World


route_patterns = [
    Gateway(handler=home),
    Gateway(handler=another),
    Gateway(handler=World),
    WebSocketGateway(handler=world_socket),
]

Если path не указан, по умолчанию используется /.

Include

Это специальный объект, который позволяет импортировать любой маршрут из любого места в приложении.

Include принимает импорт через namespace или через список routes, но не оба одновременно.

При использовании namespace Include будет искать список объектов по умолчанию route_patterns в импортированном пространстве имен, если не указано другое.

Note

Шаблон (route_patterns) работает только в том случае, если импорт выполнен через namespace, а не через routes.

src/urls.py
from esmerald import Include

route_patterns = [Include(namespace="myapp.accounts.urls", pattern="my_urls")]
src/myapp/urls.py
from myapp.accounts.urls import route_patterns

from esmerald import Include

route_patterns = [Include(routes=route_patterns)]

Если path не указан, по умолчанию используется /.

Using a different pattern

src/myapp/accounts/urls.py
from esmerald import Gateway, WebSocketGateway

from .controllers import World, another, home, world_socket

my_urls = [
    Gateway(handler=update_product),
    Gateway(handler=home),
    Gateway(handler=another),
    Gateway(handler=World),
    WebSocketGateway(handler=world_socket),
]
src/myapp/urls.py
from esmerald import Include

route_patterns = [Include(namespace="myapp.accounts.urls", pattern="my_urls")]

Include и Esmerald

Include может быть очень полезен, особенно когда цель — избежать множества импортов и огромного списка объектов, которые нужно передать в один единственный объект. Это может быть особенно полезно для создания экземпляра Esmerald.

Пример:

src/urls.py
from esmerald import Include

route_patterns = [Include(namespace="myapp.accounts.urls", pattern="my_urls")]
src/app.py
from esmerald import Esmerald, Include

app = Esmerald(routes=[Include(namespace="src.urls")])

Запуск приложения

Как уже упоминалось, мы рекомендуем использовать uvicorn в производственной среде, но это не обязательно.

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

uvicorn src:app --reload

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28720]
INFO:     Started server process [28722]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Запуск приложения с пользовательскими настройками

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

ESMERALD_SETTINGS_MODULE=myapp.AppSettings uvicorn src:app --reload

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28720]
INFO:     Started server process [28722]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
$env:ESMERALD_SETTINGS_MODULE="myapp.AppSettings"; uvicorn src:app --reload

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28720]
INFO:     Started server process [28722]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Документация OpenAPI

Esmerald также имеет встроенную документацию OpenAPI.

Esmerald автоматически запускает документацию OpenAPI, внедряя настройки OpenAPIConfig по умолчанию и предоставляет вам элементы Swagger, ReDoc и Stoplight "из коробки".

Чтобы получить доступ к OpenAPI, просто запустите вашу локальную разработку и перейдите по адресу:

  • Swagger - /docs/swagger.
  • Redoc - /docs/redoc.
  • Stoplight Elements - /docs/elements.
  • Rapidoc - /docs/rapidoc.

В этой документации есть более подробная информация о том, как настроить OpenAPIConfig здесь.

Также представлено хорошее объяснение о том, как использовать OpenAPIResponse.

Заметки

Это всего лишь очень общее демонстрационное описание того, как быстро начать и что может предложить Esmerald. Существует множество других возможностей, которые вы можете использовать с Esmerald. Наслаждайтесь! 😊

Спонсоры

В настоящее время у Esmerald нет спонсоров, но вы можете финансово помочь и поддержать автора через GitHub sponsors и стать Особенным или Легендой.