Привет! Меня зовут Никита Соболев, я core-разработчик языка программирования CPython, а так же core-разработчик фреймворка Litestar, пакета django-stubs и множества других пакетов для Django.
Сегодня я расскажу, как мы сделали самый быстрый и самый семантически корректный фреймворк для создания апишек на Джанго. Поговорим про конкурентов, покажу очень крутые интеграции, поделюсь своей философией и правилами, которые использовались для создания фреймоврка, ну накину на вентилятор для интереса.
Если хотите похоливарить в коментах на тему того, какой фреймворк самый лучший и удобный – залетайте! Обсудим.
Перед стартом
TLDR:
Исходный код (где можно бахнуть звезду): https://github.com/wemake-services/django-modern-rest
Документация: https://django-modern-rest.readthedocs.io/en/latest
Шаблон большого реального приложения: https://github.com/wemake-services/wemake-django-template
Основные фичи:
Скорость! Самый быстрый REST на Django западе: https://django-modern-rest.readthedocs.io/en/latest/pages/deep-dive/performance.html Всего на 30% медленнее FastAPI, без учета походов в базу, что сравняет производительность
Поддержка pyndatic2, msgspec, attrs, dataclasses, TypedDict, NamedTuple и множества других типов в качестве моделей. Мы не привязываемся к конкретной библиотеке и сериализатору, а даем пользователям выбор на уровне индивидуального контроллера. А еще – можно писать свои, хоть сделать свой
dmr-drf-serializer, который бы использовал существующие сериализаторы от DRF, например, при миграции кода. Ограничений никакихПоддержка sync и async режимов
Просто обычная Django. У нас нет никаких уникальных абстракций, а
Controller- просто подклассView. Все существующие плагины должны работать с нашей библиотекойПолная типизация. Типизировано всё и везде. Но типизация не заставляет вас прыгать вокруг неё, а помогает вам писать более корректный и понятный код
Content negotiation. Поддерживаем json / msgpack из коробки для обычных ответов и SSE / JsonLines для стриминга
Строгая и семантическая схема запросов и ответов. У нас в dev режиме идет строгая валидация ответов: если какого-то статуса, заголовка или поля в спеке нет, то мы падаем с ошибкой валидации. Что позволяет нам всегда держать схему точной и корректной для наших клиентов
Поддержка OpenAPI 3.1 и 3.2 из коробки. State of the Art генерация схемы
Легко переиспользовать код: мы делаем все на базе контроллеров-классов, архитектура позволяет легко менять форматы ответа и запроса, сериализаторы и другие части классов. Что позволяет с легкостью, удобством и типизацией писать плагины для DMR
Полная гибкость всего: даже можно менять формат встроенных в фреймворк ошибок, если вам нужно иметь такую возможность
Крутые инструменты для тестирования: мы используем и предоставляем интеграцию со schemathesis, для property-based тестов вашего АПИ. Одного такого теста достаточно, чтобы покрыть 90% вашего кода. Polyfactory позволяет вам с легкостью генерировать тестовые данные для тестирования ручек АПИ, а наш pytest плагин - делает весь процесс очень удобным. А еще мы поддерживаем стандартные практики
django.testпакета, если у вас уже так тесты написаныНикакого ИИ слопа. Весь код внимательно прочитан, замерен, типизирован и документирован. Однако, мы позиционируем сам фреймворк как AI-first. Мы представляем множество фичей:
llms-full.txt, Context7, DeepWiki, скилы для агентов и плагины для Claude, которые бы помогали нашим пользователям в корректном использовании нашей библиотеки с LLMками, если они ими уже пользуются. Мы даже публикуем все ломающие изменения с промтами по исправлению кода у пользователей
А теперь подробно!
Зачем?
Главный вопрос, который должен волновать каждого хорошего инженера.
Есть несколько главных причин. Давайте разберем все по-порядку.
Первая причина в самой Django. Я пользуюсь ей уже скоро почти 15 лет, знаю каждый модуль, каждую фичу, каждый плагинчик. И оно работает. Работает хорошо, стабильно, держит умеренную нагрузку (для больших нагрузок я бы взял Rust или Elixir). Имеет тысячи плагинов на любой вкус и цвет
У меня есть целый огромный репозиторий с шаблоном идеального Django приложения https://github.com/wemake-services/wemake-django-template на 2200+ звезд, где содержится весь мой наработанный опыт. Там есть буквально всё: от кучи фичей безопасности, до DX инструментов для контроля за N+1 запросами и корректной генерации тестовых данных.
Но чего в Django никогда не было – так нормального REST фреймворка. Я застал еще https://github.com/django-tastypie/django-tastypie, но сейчас есть два основных способа делать REST: django-rest-framework и django-ninja.
Я встречал довольно много людей, кто говорит что очень не любит Django. Мне всегда было интересно: а почему? Три основных пункта, которые мне говорили люди:
Мы настрадались с DRF. И тут их можно понять. Там просто вагон плохих решений. Дизайн с одним сериализатором для ответов и запросов, да, его можно менять через костыли на разные, но все равно больно. Привязка генерации АПИ к полям модели, что приводит к регулярным багам, когда ты добавил внутреннее поле в модель, а оно утекло пользователям. Полное отсутствие типизации: как типизировать
request.data? Никак. Нет встроенной OpenAPI спеки. Приходится все время использовать какие-то багованные библиотеки от других людей: тоdrf-yasg, тоdrf-spectacular, то еще что-то. Бизнес логика, которая протекает вModelSerializer.{create, update}Ужас! К сожалениюdjango-ninjaне стал нормальной заменой. Во-первых, они решили основывать свой API не на Django, а на FastAPI. И сделали API через функции, что убивает любую возможность переиспользовать код. Ведь функции нельзя наследовать и менять. И еще она очень плохо написана внутри. Ну и конечно, они прибили pydantic гвоздями, не дали нормальной кастомизации OpenAPI схемы, а JWT аутентификация вообще поставляется как форк какой-то древней DRF поделки в виде плагина поверхdjango-ninja-extra, который почти не поддерживается. Что?Проблемы с архитектурой. И тут можно частично согласиться. Документация Django писалась в середине и конце нулевых. Тогда было модно делать толстые модели, пихать всю бизнес логику во
views.py, сейчас все поменялось. Но не документация. Однако, никто не заставляет вас так делать. Я не пишу логику в моделях и вьюхах. И вы тоже не пишите. И тогда проблем не будет. Посмотрите, как вwemake-django-templateорганизована бизнес логика. Просто и расширяемо до очень больших и концептуально сложных приложенийDjango - сложная! Такое я слышал очень много раз. Я просто покажу код:
Минимальное приложение на Django + django-modern-rest
import secrets import sys import uuid import pydantic from django.conf import settings from django.core.management import execute_from_command_line from django.urls import include from dmr import Body, Controller from dmr.openapi import build_schema from dmr.openapi.views import OpenAPIJsonView, SwaggerView from dmr.plugins.pydantic import PydanticSerializer from dmr.routing import Router, path if not settings.configured: settings.configure( ROOT_URLCONF=__name__, ALLOWED_HOSTS='*', DEBUG=True, INSTALLED_APPS=['dmr', 'django.contrib.staticfiles'], STATIC_URL='/static/', STATICFILES_FINDERS=[ 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ], TEMPLATES=[ { 'APP_DIRS': True, 'BACKEND': 'django.template.backends.django.DjangoTemplates', }, ], # Secret key for tests, will be new on each run, # in production it must be the same token, kept in secret: SECRET_KEY=secrets.token_hex(), ) class UserCreateModel(pydantic.BaseModel): email: str class UserResponseModel(UserCreateModel): uid: uuid.UUID class UserController(Controller[PydanticSerializer]): async def post( self, parsed_body: Body[UserCreateModel], ) -> UserResponseModel: return UserResponseModel(uid=uuid.uuid4(), email=parsed_body.email) router = Router( 'api/', [ path('user/', UserController.as_view(), name='users'), ], ) schema = build_schema(router) urlpatterns = [ path(router.prefix, include((router.urls, 'your_app'), namespace='api')), path('docs/openapi.json/', OpenAPIJsonView.as_view(schema), name='openapi'), path('docs/swagger/', SwaggerView.as_view(schema), name='swagger'), ] if __name__ == '__main__': # Use `python THIS_FILE_NAME.py runserver` to run the example. # Then visit `http://localhost:8000/docs/swagger` to view the docs. execute_from_command_line(sys.argv)
А вот и пример нашего минимального приложения. ^^^
Можно ли назвать его сложным? Кажется, что - нет. Даже при очень большом желании. За 71 строку мы с вами создали контроллер, модели, роутинг, сгенерировали OpenAPI спеку, добавили 2 вьюхи для OpenAPI, запустили приложение.
Но в отличии от микро-фреймворков вроде FastAPI - вам не нужно думать про расширение приложения в будущем. Потому что все паттерны уже есть, все уже описано. И приложение может расти вместе с потребностями по заранее известным паттернам.
Есть ли у вас другие причины не любить Django? Поделитесь ими в комментах.
Вторая главная причина создания фреймворка в самом Python. Питон меняется. Добавление Free-Threading сделало концепцию asyncio - значительно менее актуальной. А возможно и "устаревшей".
Не верите мне? Вот цитаты уважаемых людей - других core-разрботчиков CPython:
I think that putting asyncio in the stdlib was a mistake. It froze the API and the implementation, making it much harder for alternatives (like Trio or Curio) to compete. If it had been a third-party library, it could have evolved (or died) on its own merits.
— Guido van Rossum
Virtual threads are a better way of doing concurrency than Python’s async and wait. We should add virtual threads to Python.
IMO, virtual threads offer a superior programming model to adding async and await all over your code and having to duplicate all your libraries.
— Mark Shannon, https://discuss.python.org/t/add-virtual-threads-to-python/91403
If we take a step back, it seems pretty clear to me that we have veered off course by adopting
async/awaitin languages that have real threads. Innovations like Java’s Project Loom feel like the right fit here. Virtual threads can yield when they need to, switch contexts when blocked, and even work with message-passing systems that make concurrency feel natural. If we free ourselves from the idea that the functional, promise system has figured out all the problems we can look at threads properly again.
— Armin Ronacher, https://lucumr.pocoo.org/2024/11/18/threads-beat-async-await
Кажется, что Django отлично подходит для обеих концепций. Поддержка asyncio в Django достаточно хороша, чтобы просто ей пользоваться. Но и Django не полностью завязана на asyncio, как тот же FastAPI. Кажется, что и тут более стратегически верно продолжать развивать Django и инструменты рядом.
Пример
Не буду утомлять вас большим количеством примеров, потому что я понимаю, что каждый из вас может зайти в официальную документацию и прочитать. Но несколько интересных моментов все же стоит показать и тут.
Давайте начнем от "противного". Вот так FastAPI огранизует парсинг данных из запроса, официальный пример из их документации:
from typing import Any from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str @app.put("/items/{item_id}") async def update_item( item_id: int, q: str | None = None, item: Item | None = None, ) -> dict[str, Any]: results = {"item_id": item_id} if q: results.update({"q": q}) if item: results.update({"item": item}) return results
Что здесь что? Откуда берется q? Откуда item_id? Почему item резко появляется из .body, а не из path или query как раньше? Как нам парсить Headers? А как Cookies? Да, вы правильно поняли, что там везде разный набор правил и разная семантика.
Сравним с:
from typing import Any from dmr import Body, Controller, Path, Query from dmr.plugins.pydantic import PydanticSerializer from pydantic import BaseModel class BodyItem(BaseModel): name: str class PathItem(BaseModel): item_id: int class QueryItem(BaseModel): q: str | None = None class ItemController(Controller[PydanticSerializer]): async def put( self, parsed_body: Body[BodyItem | None], parsed_query: Query[QueryItem], parsed_path: Path[PathItem], ) -> dict[str, Any]: results = {'item_id': parsed_path.item_id} if parsed_query.q: results.update({'q': parsed_query.q}) if parsed_body.item: results.update({'item': parsed_body.item}) return results
Всё четко и сразу понятно: какие компоненты парсят что, откуда, в какую модель. Более того, мы сразу выносим всю возможную валидацию из самого метода в модели. Например: если нам нужно сказать, что нам может прийти или q или v в качестве query. Сразу делаем в модели. А как парсить заголовки и cookies? Да, вы угадали: parsed_headers: Headers[HeaderModel] и parsed_cookies: Cookies[CookieModel]. Можно писать свои компоненты для парсинга и наследовать существующие (например для полной кастомизации OpenAPI схемы).
Как оно работает?
Теперь, когда мы поняли "почему", посмотрели примеры, давайте глубоко нырнем в вопрос "как".
В основе django-modern-rest лежит несколько простых идей:
Мы даем людям возможность выдрать сериализацию из общего слоя приложения в отдельный класс с простым API, чтобы можно было использовать любые библиотеки для парсинга байтов в питоновские примитивы, а потом в какую-то модель (или сразу в модель, если хотите). Пример сериализатора. За счет чего получаем большую гибкость
Мы никак не лезем в модели. Мы не знаем про них ничего. Ни как они определяются, ни как получать из них OpenAPI схему. Только ваш текущий сериализатор знает, как работать с вашими моделями. Мы не лезем в
strict=Trueвpydantic, мы не добавляем никаких дополнительных абстракций. За счет чего получаем большую понятностьМы делаем все подготовительные действия из возможных в import-time. Например: построение
TypeProviderдля валидации, проверка корретности определения самих контроллеров, создание всех промежуточных объектов и тд. В runtime мы только получаем данные, парсим и валидируем их, возвращаем ответ. Пример прогрева кеша перед запуском. За счет чего мы получаем скоростьВсе должно быть в спеке. Все возможные коды ошибок, все возможные ответы, все возможные запросы. Я слишком долго в своей жизни правил баги, когда мы держали какие-то части АПИ без спецификации, а потом они внезапно переставали работать. Спека – источник истины. Для того мы и валидируем в dev/test режиме все ответы от нашего АПИ. Но мы часто можем не описать данные ответы руками. Если мы знаем, что
authможет упасть с 401, то мы просто добавляем в компонентauthданный код. А потом его добавляем в спеку. За счет чего мы получаем корректностьDjango должна оставаться Django. Все наши классы - просто обычные Django View классы. С ними работает все, что работает с Django View. Даже роутинг мы используем стандартный. Только у нас есть собственный вариант замены (с полным сохранением API), который работает в 51 раз быстрее. Такой дизайн позволяет полностью переиспользовать весь тот богатый мир Django плагинов. За счет чего мы получаем совместимость и целый мир готовых библиотек
Что еще есть интересное?
Переиспользование кода
Мы все 1000 раз копировали код из проектов типа https://indominusbyte.github.io/fastapi-jwt-auth/usage/basic/, где нужно буквально влить себе в проект тонну бойлерплейта, просто чтобы получить несчастный JWT токен. А еще мы все страдали от кода вида https://github.com/jazzband/djangorestframework-simplejwt/blob/35dd45723d6bd057fd57ebb2d12e124b44a875c1/rest_framework_simplejwt/serializers.py#L121-L150 где ехал import_string через import_string, которые выполняются прямо в рантайме. Хотим мы такого? Нет.
There must be a better way! (c) Raymond Hettinger
В django-modern-rest контроллер считается абстрактным, пока в него не прокинули все необходимые типовые переменные. Что дает нам возможность писать Generic заготовки вроде:
class ObtainTokensAsyncController( _BaseTokenController[_SerializerT], Generic[_SerializerT, _ObtainTokensT, _TokensResponseT], ): responses = ( ResponseSpec( return_type=ErrorModel, status_code=HTTPStatus.UNAUTHORIZED, ), ) @modify(status_code=HTTPStatus.OK) async def post(self, parsed_body: Body[_ObtainTokensT]) -> _TokensResponseT: return await self.login(parsed_body) async def login(self, parsed_body: _ObtainTokensT) -> _TokensResponseT: user = await aauthenticate( self.request, **(await self.convert_auth_payload(parsed_body)), ) if user is None: raise NotAuthenticatedError self.request.user = user return await self.make_api_response() @abstractmethod async def convert_auth_payload( self, payload: _ObtainTokensT, ) -> ObtainTokensPayload: raise NotImplementedError @abstractmethod async def make_api_response(self) -> _TokensResponseT: raise NotImplementedError
Что тут происходит? Мы объявляем Generic контроллер, в котором есть 3 типовые переменные:
_SerializerTдля конкретнного класса сериализатора_ObtainTokensTдля типа входных данных, они могут быть разными. Например:{"username": "str", "password": "str"}или{"email": "str", "token": "str"}_TokensResponseTдля формата ответа: он тоже может быть любой, например:{"access": "str", "refresh": "str"}
А дальше нам просто можно воспользоваться данным абстрактным контроллером для нашей собственной реализации:
from dmr.security.jwt.views import ObtainTokensSyncController class ObtainTokensPayload(TypedDict): username: str password: str class ObtainTokensResponse(TypedDict): access_token: str refresh_token: str class ObtainAccessAndRefreshSyncController( ObtainTokensSyncController[ PydanticSerializer, ObtainTokensPayload, ObtainTokensResponse, ], ): @override def convert_auth_payload( self, payload: ObtainTokensPayload, ) -> ObtainTokensPayload: return payload @override def make_api_response(self) -> ObtainTokensResponse: now = dt.datetime.now(dt.UTC) return { 'access_token': self.create_jwt_token( expiration=now + self.jwt_expiration, token_type='access', # noqa: S106 ), 'refresh_token': self.create_jwt_token( expiration=now + self.jwt_refresh_expiration, token_type='refresh', # noqa: S106 ), }
Что мы тут делаем?
Определяем входные и выходные модели
Наследуемся от нашего абстрактного контроллера, передаем ему нужные типы через типовые параметры
Переопределяем два метода, чтобы сконвертировать данные запроса и ответа в нужные нам
Готово! Мы не написали ни строчки boilerplate кода, только то, что нам реально нужно.
А в итоге получили красивый, быстрый, типизированный и расширяемый код.
Стримим SSE или JsonLines
Сейчас в разработке никуда без стриминга событий. LLMки их используют, метрики, логи, трейсы, финансы, гео-события, и т.д.
Конечно же нам нужны такие возможности. Выглядит оно вот так:
import dataclasses from collections.abc import AsyncIterator from dmr.plugins.msgspec import MsgspecSerializer from dmr.streaming.sse import SSEController, SSEvent @dataclasses.dataclass(frozen=True, slots=True) class _User: email: str class UserEventsController(SSEController[MsgspecSerializer]): async def get(self) -> AsyncIterator[SSEvent[_User]]: return self.produce_user_events() async def produce_user_events(self) -> AsyncIterator[SSEvent[_User]]: # You can send any complex data that can be serialized # by the controller's serializer, # all SSEvent fields can be customized: yield SSEvent( _User(email='first@example.com'), event='user', ) yield SSEvent( _User(email='second@example.com'), event='user', )
Чтобы стримить что-то, нам потребуется:
Отнаследоваться от нужного контроллера:
SSEControllerОпределить источник событий:
produce_user_eventsВернуть из него
AsyncIteratorВернуть источник событий из нашей ручки АПИ
Мы великолепны!
Однако, данная фича будет работать только на ASGI, в разработке можно использовать и WSGI, но стриминг быстро съест все коннекты в WSGI. Так что мы требуем ASGI. Он идет в поставке с Django и хорошо работает.
Современные подходы к тестам
Скажу честно, что я украл данный подход из Litestar. Мне очень нравится библиотека polyfactory. Она позволяет почти для любых типов моделей генерировать тестовые данные.
Если у нас есть вот такой контроллер:
class UserCreateModel(pydantic.BaseModel): email: str age: int class UserModel(UserCreateModel): uid: uuid.UUID class UserController(Controller[PydanticSerializer]): def post(self, parsed_body: Body[UserCreateModel]) -> UserModel: return UserModel( uid=uuid.uuid4(), age=parsed_body.age, email=parsed_body.email, )
То мы можем вот так его протестировать:
import json from http import HTTPStatus from dirty_equals import IsUUID from django.http import HttpResponse from polyfactory.factories.pydantic_factory import ModelFactory from dmr.test import DMRClient from examples.testing.pydantic_controller import UserCreateModel class UserCreateModelFactory(ModelFactory[UserCreateModel]): """Will create structured random request data for you.""" __check_model__ = True def test_create_user(dmr_client: DMRClient) -> None: request_data = UserCreateModelFactory.build().model_dump(mode='json') request = dmr_client.post('/url/', data=request_data) assert response.status_code == HTTPStatus.CREATED assert response.content.json() == { 'uid': IsUUID, **request_data, }
Если у нас таких фабрик становится много, то они легко превращаются в pytest фикстуры.
Тесты получаются удобными, простыми, достаточно быстрыми и расширяемыми. А штуки вроде pytest-randomly позволяют сделать их еще и воспроизводимыми.
Считаем OpenAPI coverage
Во всех моих проектах всегда требование 100% покрытия кода. Однако, что делать дальше? Если ты уже все покрыл? Дальше остается выдумывать новые полезные метрики. Такие как мутационное тестирование.
Или такие как tracecov или "Покрытие OpenAPI спеки". Выглядит оно вот так:

Что оно показывает?
Какие операции мы дергали во время тестирования приложения?
Какие параметры мы отправляли из тех, что разрешает спецификация?
Сколько разных примеров протестировали, из тех, что указаны в спецификации?
Сколько видов ответов получили из возможных?
Данная фича - очень крутая! Потому что в отличии от "покрытия кода", она показывает реально важную для ваших клиентов метрику: а сколько АПИ запросов у вас реально работают так, как нужно?
Повторить данную фичу без нашей строгой семантической спецификации будет очень сложно. Ведь сначала надо построить спеку, прежде чем ее тестировать.
А как она считается? У нас прямо в фикстуре dmr_client, который мы используем для создания интеграционных запросов, стоит интеграция, которая такое считает. Если вы используете schemathesis, то результаты тестов суммируются. В конфиге pytest вы можете указать:
# Tracecov: "--tracecov-format=text,html,markdown", "--tracecov-fail-under-operations=100", "--tracecov-fail-under-examples=100", # TODO: set value to 100 "--tracecov-fail-under-parameters=90", "--tracecov-fail-under-keywords=90", "--tracecov-fail-under-responses=50",
Чтобы тесты автоматически падали, если ваше покрытие спеки будет падать.
Использование ИИ
Я не большой поклонник ИИ. ИИ очень много ошибается, требует очень много сюсюкания, а самое главное - лишает меня удовольствия писать код самому.
Но, я прекрасно понимаю, что многие люди любят агентов и используют их в работе и в своих хобби проектах. Вот почему мы позиционируем django-modern-rest как AI-First фреймворк.
Давайте покажу на примере. Есть классическая задача для REST фреймворков. Дана OpenAPI спека, нужно сгенерить код; раньше все писали такие генераторы руками. Они быстро устаревали. Вечно ломались, дорабатывать их было очень неудобно. Сейчас все изменилось. Теперь агент может сделать такое полностью сам, нам только нужно предложить для него контекст и скилы.
Для контекста мы используем:
llms.txt или llms-full.txt - даешь одну ссылку ЛЛМ, и она знает
кунг-фуdjango-modern-restContext7 - как еще один формат. Там же можно бесплатно початиться с ЛЛМ про фреймворк, попросить написать какие-то примеры кода. Удобно. Там же можно найти все наши скилы: https://context7.com/wemake-services/django-modern-rest?tab=skills
А еще мы заморочились с DeepWiki: https://deepwiki.com/wemake-services/django-modern-rest
На мой вкус – она отлично помогает дополнить документацию. А на вопросы "почему так?" - вообще дает очень полные и подробные ответы со ссылками на документацию и исходники. Регулярно пользуюсь для других проектов
Каждую страницу доки можно сразу скинуть как контекст в ChatGPT или Claude:

Мы еще находимся в альфе. И кое-что ломаем. Все ломающие изменения мы публикуем в двух форматах: текстовом (для людей) и в виде промпта для агентов. Пример последнего релиза: https://github.com/wemake-services/django-modern-rest/releases/tag/0.4.0
Копируешь промпт - агент за тебя меняет все части проекта, которые мы сломали. Очень удобно, пользователи почти не страдают.
Ну и конечно же мы подготовили два очень важных скила:
https://github.com/wemake-services/django-modern-rest/tree/master/.agents/skills/dmr-from-drf для миграции на
django-modern-restс DRFhttps://github.com/wemake-services/django-modern-rest/tree/master/.agents/skills/dmr-from-django-ninja для миграции на
django-modern-restсdjango-ninja
То есть буквально: берете ваш старый код на django-ninja, добавляете скилл, говорите: "мигрировай!". И оно мигрирует.
Давайте покажу. У меня как раз есть проектик старый на django-ninja.
Спор о лучших агентах можно вести долго, я для демо возьму https://gitverse.ru/features/gigacode/install/, потому что он доступен бесплатно, без КВНа в России, есть плагинчик на пичарм (который самый популярные редактор / ide по моему опыту). Давайте его и возьмем для примера.
Промптинг по шагам:
Прочитай эти два источника и только подтверди, что прочитал: 1. Документация: https://django-modern-rest.readthedocs.io/llms-full.txt 2. Скил миграции: https://github.com/wemake-services/django-modern-rest/blob/master/.agents/skills/dmr-from-django-ninja/SKILL.md После прочтения выведи: - Краткое summary документации (5-7 ключевых концептов) - Список всех правил из скила (нумерованный список, дословно) - Напиши "Готов к анализу кода" Больше ничего не делай.

А затем в отдельном шаге:
Ты мигрируешь API с django-ninja на django-modern-rest. ПРАВИЛА: - Используй ТОЛЬКО то, что есть в документации и скиле (ты их уже прочитал) - Если чего-то не знаешь или потерял контекст — СТОП, напиши "Мне нужно уточнение: [вопрос]" - Никогда не угадывай API — только документация - После каждого шага жди моего подтверждения "продолжай" ПРОЦЕСС: Шаг 1: Проанализируй мой код и составь список всех django-ninja конструкций, которые нужно заменить. Выведи таблицу: | django-ninja | django-modern-rest аналог | уверенность (есть в доках / не нашёл) | Шаг 2 (после моего ок): Составь пошаговый план миграции — каждый пункт это один атомарный файл или блок Шаг 3+ (по одному шагу за раз): Реализуй один пункт плана → покажи diff → жди "продолжай"

Тут нужно будет особо внимательно читать то, что он предлагает. Поправить его сейчас сильно проще, чем потом!
Что дальше:
Потребуется какое-то время, пока агент работает, процесс далеко не мгновенный
Ему нужно много внимания, все промпты про то, чтобы он не делал фигню сам, а спрашивал вас, если что-то не знает / потерял контекст
Каждый diff все равно надо ревьюить глазками, в вашем коде никто не освобождал вас от ответственности за него
Потом применить тулинг сверху: всякие
ruff,black, тдДо состояния самолета придется доработать поезд напильником
Теперь у вас нет отговорок про легаси! Бегом мигрировать на новые модные фреймворки! 🌚️️(шутка, обязательно оценивайте технические решения с холодным расчетом, взвешивайте риски, спрашивайте свою команду)
Вместо завершения
Как-то не поднимается рука назвать данную главу "Завершение", потому что у нас столько всего впереди!
Продолжим писать примеры, интеграции, и дорабатывать документацию
Продолжим улучшать OpenAPI спецификацию
Попробуем сделать быстрее саму Django в некоторых местах
Ускорим и улучшим парсинг форм через multipart, как опциональная зависимость или конфигурация
Мы хотим компилировать некоторые части фреймворка с mypyc, чтобы добиться бесплатного прироста производительности
Возможно какие-то критичные части вынесем вообще в Rust
Подумаем как сделать новые WebSocket для Django вместе с бывшими участниками команды django-channels
Надеюсь, что вам было интересно. Потому что мне - было! Работа над данным фреймворком принесла мне кучу положительных моментов.
Что можно сделать для поддержки проекта?
Поставить звездочку https://github.com/wemake-services/django-modern-rest/ Давайте добьем до 1000!
Вступить в наше сообщество в ТГ: https://t.me/opensource_findings
Закинуть мне на пиво за 4+ месяцев фултайм работы над проектом, донат начинается от всего 100 рублей: https://boosty.to/sobolevn
Открывать задачки на гитхабе с обратной связью, багами, идеями для фичей. У нас пока альфа, все будем оперативно править / дорабатывать
Давайте в комментах выясним, какие фреймворки (на питоне и не только) - самые лучшие!
