Обновить

Комментарии 33

Я начну - salvo. Хорош как axum (потому что внутри axum), но не нужно самому изобретать велосипеды.

Кстати, комрад @fenrir1121 - сделал очень много крутого для нашей доки! Спасибо большое! <3

Хорошее решение. Вот бы ещё такое же для FastAPI )

Какой хороший троллинг, посмеялся :)

Не хочу показаться невежливым, но ваш пример "от противного" отдаёт черри-пикингом. Взял на себя труд привести более современную реализацию, (в т.ч. с заголовками, куками, etc)

from typing import Any, Annotated

from fastapi import FastAPI, Query, Body, Path, Header, Cookie
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str


@app.put("/items/{item_id}")
async def update_item(
        item: Annotated[Item, Body(description="...")],
        query_param: Annotated[str, Query(description="...")],
        item_id: Annotated[int, Path(description="...")],
        header_param: Annotated[int, Header(description="...")],
        cookie_param: Annotated[str, Cookie(description="...")]
) -> dict[str, Any]:
    results = {"item_id": item_id}
    if query_param: results['query_param'] = query_param
    if item: results['item'] = item
    if header_param: results['header_param'] = header_param
    if cookie_param: results['cookie_param'] = cookie_param
    return results

Также нельзя не заметить, что в FastApi в интерфейсе функции фактически описывает анонимный pydantic RequestType и его поля, со всеми доступными возможностями pydantic.

В вашем же примере, чтобы описывать примитивы вида q: str - пришлось делать отдельные классы-пустышки на один параметр.

Да, вы правы. Можно писать по-другому. Но и от себя я ничего не придумывал. Ссылка на доку, откуда я взял пример - прямо там. На мой взгляд явные разные модели лучше анонимных, где все идет вперемежку: тело, заголовки, пути. Но на вкус и цвет - все оголенные провода разные :)

Если вы пытаетесь продать фреймворк критикуя конкурентов, будьте готовы, что к вам придут евангелисты этого конкурента с актуальными исходниками и размажут ваши аргументы :)

Я же сам пригласил всех в комментарии обсуждать, какой фреймворк самый лучший :) Мы все тут, чтобы вдоволь понабрасывать!

Искал этот коммент :)

Хочу дополнить, что FastAPI вполне неплохо работает с синхронным кодом, запуская обработчики в отдельных потоках

Да, только количество потоков в пуле ограничено и это всё равно накладно - запускать синхронные обработчики в thread pool в изначально асинхронном контексте. В общем, лучше вообще не делать синхронные обработчики и dependencies в FastAPI, потому что явный вызов to_thread.run_sync хотя бы видно, а когда это делается неявно, вы это уже не контролируете.

https://github.com/Kludex/fastapi-tips?tab=readme-ov-file#2-be-careful-with-non-async-functions

Litestar

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

Зачем вот это вообще сделано? Типы для параметров в путях? В строках!

@get("/user/{user_id:int}", sync_to_thread=False)
def get_user(user_id: int) -> User:
    return User.model_validate(USER_DB[user_id])

The types declared in the path parameter and the function do not need to match 1:1 - as long as parameter inside the function declaration is typed with a “higher” type to which the lower type can be coerced, this is fine.

Что? Они издеваются? Кто вообще это придумал и зачем? Я читаю документацию и не понимаю, какую проблему они решают. Но всех запутать и создать головной боли из ничего на ровном месте им удалось.

Dependency injection там тоже сделан так, что код придется править в нескольких местах при каких-то изменениях, и всё через строки, что ломает любую интроспекцию кода в IDE.

Не удивительно, что им почти никто не пользуется. Но DRF, конечно, никто не переплюнет по проклятости.

Давайте начнем от “противного”. Вот так FastAPI огранизует парсинг данных из запроса

Там тоже можно проаннатировать типами Path, Body, Query, Header, Cookie. И в какой-то версии они наконец-то сделали возможность всё это описывать в Pydantic моделях если параметров много, а не только тип Body.

msgspec

Очень крутая библиотека, очень быстрая. Жаль, что в FastAPI всё прибито гвоздями к Pydantic.

Согласен со всеми пунктами (кроме пожалуй типов в параметрах путей). DI мы переделаем к 3.0 версии. К сожалению, там поменялись ключевые разработчики несколько лет назад. И проект немного подвис. Он мог бы быть прям одним из лучших фреймворков, но пока - скорость развития оставляет желать лучшего :(

Посмотрел документацию по диагонали, статью тут, выглядит так, что вы сражаетесь с FastAPI, хотя ваш конкурент это DRF.

Откройте документацию по ненавистной DRF, которая утилизирует полностью компоненты Django, у вас же, я не вижу ничего связанного даже с моделями, зачем мне Pydantic, если мои данные уже описаны в django models? Работа с куками, файлами, вы полностью принесли FastAPI решение, а не джанго.

Не понимаю, чем он лучший у вас. Ладно бы был вне Django, тогда можно было с FastAPI сравнить

Использование моделей во вью, по примеру с DRF - плохая практика, потому что смешивается слой хранения и слой API. В этом случае API привязано к модели хранения, любое ее изменение ломает контракт. Плюс валидация начинает размазываться между слоями. Модели Django про хранение и инварианты базы, а Pydantic-схемы про контракт API и валидацию входа.

Возможно, DRF дженерики, в которых прям во view используется `queryset = ...` и удобен для приложения уровня "мой первый блог", но в чем-то больше такой подход начинает ограничивать, поэтому разделение схем и моделей не избыточность, а вполне нормальная практика.

хотя ваш конкурент это DRF.

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

Более того, сам по себе encode тоже мертв. Истории с httpx и starlette его добьют.

Откройте документацию по ненавистной DRF, которая утилизирует полностью компоненты Django

В том-то и проблема, что нет :(

DRF для всего требует свой отдельный АПИ; простой пример: https://github.com/carltongibson/django-filter?tab=readme-ov-file#usage-with-django-rest-framework Вон даже отдельная страничка доки есть. А нам такое не нужно, мы можем просто использовать django-filters напрямую.

зачем мне Pydantic, если мои данные уже описаны в django models?

Ваши данные не описаны в моделях. В моделях описаны ваши модели - структура таблиц в БД :)

Данные, которые мы передаем по АПИ, ортогональны моделям. Да, иногда часть полей и схем совпадает изначально. Но дальше начинается: совпадает, да не совсем, требует дополнительно логической или структурной валидации, какие поля начинают быть зависимы друг от друга, какие работают только на запись, какие-то только на чтение, какие-то поля меняются отдельно от модели, какие-то поля модели меняются отдельно от слоя сериализации, какие-то модели требуют разного представления в разных апишках. А потом появляется версионирование. Суть вы уловили.

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

Хотите сделать явный мапинг из модели в сериализатор для примера hello world приложения? Пожалуйста: https://django-modern-rest.readthedocs.io/en/latest/pages/integrations.html Такая фича тоже есть. Просто она не является основной приложения, а скорее редким кейсом.

Работа с куками, файлами, вы полностью принесли FastAPI решение, а не джанго.

Нет, я как раз поностью сохранил Django подход. https://django-modern-rest.readthedocs.io/en/latest/pages/components/files.html Буквально внутри используется стандартный request.FILES. Все дефолтные настройки загрузки файлов работают.

Аналогично с парсингом кук, сама логика на 1 строку: просто возвращаем request.COOKIES

https://github.com/wemake-services/django-modern-rest/blob/b31ade395e4ac6491728dd49e6b5510d290dfb3a/dmr/components.py#L727

Не понимаю, чем он лучший у вас.

По скорости, гибкости, корректности, качеству спеки, продвинутости инструментов тестирования.

Отсутствие глубокой интеграции с Django ORM (как в DRF) это и плюс, и минус DMR. Плюс - вы можете юзать SQLAlchemy или сырые запросы. Минус - вам придется руками писать маппинг из ORM-моделей в Pydantic-схемы, что возвращает нас к тоннам бойлерплейта, от которого мы хотели уйти

Вот прямо убедили! FastAPI теперь точно не буду использовать.
Но вот PostgREST, точно продолжу.

О, ты все-таки переделал передачу схемы из generic атрибутов в сигнатуру функции, посмотрю исходники еще разок, выглядит поживее

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

А вот для новых проектов выглядит как отличная альтернатива связке FastAPI + SQLAlchemy, если вам нужна админка и ORM от Джанги из коробки)

Очень пафосно в подаче и мертворождено поздно. Как python программисту, да, вероятно этот продукт сойдет. Как джанго программисту - нет. В чем отличие - уже было сказано выше. Вы не утилизуете Django, а создаете свои объекты и классы. Это не плохо, нет, это просто не django-ish.

Например:

Валидация.

Есть такой концепт “Django-Forms” и “Django-ModelForms”. При правильной работе они соизмеримы по скорости валидации с Pydantic 2, а при неправильной работе с Pydantic - они быстрее, (я рассказывал про это на DjangoCon/PyCon в 2024-2025)

FastApi стиль - вкатить Pydantic для валидации. Тем кто наелся, но пишет на Python - посмотреть msgspec, marshmellow. Тем кто работает с Django - понять как работают Form/ModelForm. Это тот самый “ортогональный” объект по отношению к Моделям.

Сериализация.

FastApi стиль - вкатить Pydantic еще и для сериализации. Тем кто наелся, но пишет на Python - вероятно, написать “явный мапинг из модели в сериализатор” Тем кто работает с Django - понять как работает Django Serialization Framework.

И т.д.

Можно много писать за и против. Я рад, что еще кто-то работает с альтернативами для DRF, жаль только что это далеко от Django.

Вы не утилизуете Django, а создаете свои объекты и классы.

Простите, я не понял сути вопроса :)

Я же буквально создаю подкласс django.views.View, почему я не утилизирую Джангу?

Есть такой концепт “Django-Forms” и “Django-ModelForms”.

Но ведь формы нужны для html страничек. Они потому и называются: формы :)

Формами парсить json или xml - довольно сложно, если вообще возможно. У вас есть какой-то публичный пример, как вы такое делаете? Если есть, можно сделать сериализатор-плагин для DMR. Нам все равно, что и откуда парсить. В том и суть дизайна.

FastApi стиль - вкатить Pydantic для валидации.

Для валидации - мы его не советуем использовать. Валидация должна быть частью бизнес логики. А не частью фреймворка.

Тем кто работает с Django - понять как работает Django Serialization Framework.

Так в том и штука, вы можете его использовать с DMR, еще раз: нам все равно что и откуда парсить. Мы не навязываем никаких подходов. Пишите свой плагин с 4 методами. И все, используете!

Дополните пожалуйста критику какими нибудь примерами или какими-то деталями.

Почему плохо использовать Pydantic для валидации входящих данных? Мне удобно использовать их в сигнатурах функций сервисного слоя, получается очень выразительно. Не представляю как их заменить там Form/ModelForm, тем более это и не формы вовсе, да и лаконичности в них маловато.

Валидация != Сериализация. Пример: валидация = проверка уникальности email в БД. Такое только в бизнес логике.

мой вопрос был к danilovmy :) Совмещать валидацию с моделями в сервисном слое легко и приятно. А за django-modern-rest большое спасибо, выглядит интересно.

Ваш фреймворк производит отличное впечатление и имеет все шансы составить конкуренцию DRF! Подскажите, пожалуйста, предусмотрен ли у вас аналог механизма permissions из DRF?

Нет, permissions - не часть фреймворка :( Permissions - часть вашей бизнес логики. Она довольно сильно связана с ролевой моделью, владением объектов и прочим. Такое куда удобнее делать в "сервисном" или "usecase" слое. Тащить логику во фреймворк - мы 100% никогда не будем.

Но в целом - проверка доступа останется одной строчкой внутри вашей логики, так что сложностей не будет :)

никогда не понимал хейта в сторону фастапи, это же просто инструмент один из

пилю в таком виде все свои контроллеры уже который год

уж извините что это не пайтон вээээй, без негатива друг

from fastapi import Depends

from Application.Dtos.Catalogs.Requests.CatalogCreate import CatalogCreate
from Application.Dtos.Catalogs.Requests.CatalogUpdate import CatalogUpdate
from Application.Services.CatalogService import CatalogService
from Application.Services.TokenService import TokenService
from Startup.Services.FastApiService import FastApiService

class CatalogsController : 

    _fastApiService:FastApiService = None
    _catalogService:CatalogService = None,
    _tokenService:TokenService = None

    def __init__(
        self,
        fastApiService:FastApiService,
        catalogService:CatalogService,
        tokenService:TokenService) :
        self._fastApiService = fastApiService
        self._catalogService = catalogService
        self._tokenService = tokenService
        self._RegisterEndpoints()
        
    def _RegisterEndpoints(self) : 
        self._fastApiService._fastApi.add_api_route(
                    "/api/catalogs/",
                    endpoint=self.GetAll,
                    methods=["GET"])

        self._fastApiService._fastApi.add_api_route(
                    "/api/catalogs/{titleTranslit}/",
                    endpoint=self.GetByTitleTranslit,
                    methods=["GET"])

        self._fastApiService._fastApi.add_api_route(
                    "/api/catalogs/",
                    endpoint=self.Create,
                    methods=["POST"],
                    dependencies=[Depends(self._tokenService.VerifyRequest)])

        self._fastApiService._fastApi.add_api_route(
                    "/api/catalogs/",
                    endpoint=self.Update,
                    methods=["PUT"],
                    dependencies=[Depends(self._tokenService.VerifyRequest)])

        self._fastApiService._fastApi.add_api_route(
                    "/api/catalogs/{id}/",
                    endpoint=self.Delete,
                    methods=["DELETE"],
                    dependencies=[Depends(self._tokenService.VerifyRequest)])

    async def GetAll(self) : 
        return self._catalogService.GetAll()
    
    async def GetByTitleTranslit(self, titleTranslit:str) :
        return self._catalogService.GetByTitleTranslit(titleTranslit)

    async def Create(self, request:CatalogCreate) : 
        return self._catalogService.Create(request)

    async def Update(self, request:CatalogUpdate) : 
        return self._catalogService.Update(request)

    async def Delete(self, id:int) : 
        return self._catalogService.Delete(id)

Выглядит прикольно! Спасибо, что поделились

Почему core-разработчик, а не коре-девелопер?

А фиг его знает, как оно правильно на русский переводится. Кто-то говорит "разработчик ядра", но я как-то не фанат такого перевода :(

Хорошо получилось, попользуемся -цать лет, пока оно тоже не обрастет творческим мхом забытых фич и не появится что-то новенькое. ;) Интересно, что сам Джанго так и продолжает работать без особых претензий.

Так и есть!

Наконец-то что-то свежее! Спасибо!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации