Зачем нужен новый REST API для Django?
Последний месяц я крайне увлечен созданием https://github.com/wemake-services/django-modern-rest
(Кстати, у нас уже >200 звезд и 26 контрибьюторов при 0 релизов)
Ключевой вопрос – зачем? Что будет нового?
Во-первых, я крайне сильно люблю Django. Основная причина – под него есть буквально любые библиотеки. Все то, что в FastAPI нужно героически писать руками – в джанге уже давно есть и работает.
Но, есть несколько основных причин, почему люди думают, что не любят джангу:
1. DRF, тут без лишних слов – он ужасен. Нет типизации, нет async, очень сложно работать с ним в сложных случаях
2. Многие считают, что джанга - сложная. Однако, она как FastAPI или Litestar может спокойно уместиться в один файл: https://django-modern-rest.readthedocs.io/en/latest/pages/micro-framework.html
3. Есть критика за архитектуру: тут нечего особо даже комментировать. На любом фреймворке можно писать хорошо, на любом можно писать плохо. Слой фреймворка очень тонкий
4. Медленный. А вот тут остановимся подробнее.
На скринах выше можно сравнить, что с
Во-вторых, Django на длительный срок был в плену DRF. Который морально устарел еще в середине десятых.
Потом появилась
Вот такие проблемы мы решаем.
Одной строкой:
- Супер строгая OpenAPI схема и валидация при разработке, что мы ее соблюдаем
- Полная типизация всего
- Нормальный способ создания АПИ, без god-functions a-la FastAPI
-
- Быстро:
- Пидантик,
- Полная поддержка всего существующего в Django. От декораторов до миддлварь (с полной валидацией и OpenAPI схемой!)
- OpenAPI схема из коробки
- Удобное тестирование с polyfactory
- property-based тесты вашего АПИ одной командой благодаря schemathesis
Что еще будет:
- RSGI (да, на #rust скорее всего) под granian, в теории можем побить FastAPI после такого
- SSE с автоматической OpenAPI спекой и умной валидацией схемы
- JWT или любая другая аутентификация
- Какие-то части (скорее всего валидацию) мы компильнем cython, чтобы было еще быстрее
Modern? Modern!
Осталось дождаться. Релиз скоро!
И еще скоро будут несколько новых видео.
Мы регулярно постим задачки в @opensource_findings_python, так что можно нам помочь.
А если кто хочет мне закинуть на развитие опенсорса (за написание 32500 строк кода за 2 недели), то мой бусти открыт: https://boosty.to/sobolevn
Ну и звезды можно ставить, конечно же. Там посмотрите, какие люди нас уже советуют.
Большое спасибо Александру, Алексею, Роману, Максиму и всем остальным за неоценимую помощь. Пацаны, без вас никак! Лучшее сообщество!
Обсуждение: а чего бы вам хотелось в modern rest фреймворке? Чего сильно не хватает сейчас?
Последний месяц я крайне увлечен созданием https://github.com/wemake-services/django-modern-rest
(Кстати, у нас уже >200 звезд и 26 контрибьюторов при 0 релизов)
Ключевой вопрос – зачем? Что будет нового?
Во-первых, я крайне сильно люблю Django. Основная причина – под него есть буквально любые библиотеки. Все то, что в FastAPI нужно героически писать руками – в джанге уже давно есть и работает.
Но, есть несколько основных причин, почему люди думают, что не любят джангу:
1. DRF, тут без лишних слов – он ужасен. Нет типизации, нет async, очень сложно работать с ним в сложных случаях
2. Многие считают, что джанга - сложная. Однако, она как FastAPI или Litestar может спокойно уместиться в один файл: https://django-modern-rest.readthedocs.io/en/latest/pages/micro-framework.html
3. Есть критика за архитектуру: тут нечего особо даже комментировать. На любом фреймворке можно писать хорошо, на любом можно писать плохо. Слой фреймворка очень тонкий
4. Медленный. А вот тут остановимся подробнее.
На скринах выше можно сравнить, что с
django-modern-rest, без нескольких будущих оптимизаций (привет cython и rust!), голая джанга начинает выглядеть не так уж и медленнее FastAPI: бенчи.Во-вторых, Django на длительный срок был в плену DRF. Который морально устарел еще в середине десятых.
Потом появилась
django-ninja, которая хоть и намного лучше, но все равно не решает главную задачу: интеграция с существующими тулами. Конечно, если затащить FastAPI дизайн в чужой мир, то ничего не будет нормально работать 🌚️️Вот такие проблемы мы решаем.
Одной строкой:
- Супер строгая OpenAPI схема и валидация при разработке, что мы ее соблюдаем
- Полная типизация всего
- Нормальный способ создания АПИ, без god-functions a-la FastAPI
-
async без sync_to_async- Быстро:
msgspec для парсинга json (самый быстрый способ в питоне), практически 0 работы в рантайме (все делаем при импорте), оптимизации роутера (быстрее дефолтного в 51 раз)- Пидантик,
msgspec, или любой другой способ выражать модели. Хоть adaptix- Полная поддержка всего существующего в Django. От декораторов до миддлварь (с полной валидацией и OpenAPI схемой!)
- OpenAPI схема из коробки
- Удобное тестирование с polyfactory
- property-based тесты вашего АПИ одной командой благодаря schemathesis
Что еще будет:
- RSGI (да, на #rust скорее всего) под granian, в теории можем побить FastAPI после такого
- SSE с автоматической OpenAPI спекой и умной валидацией схемы
- JWT или любая другая аутентификация
- Какие-то части (скорее всего валидацию) мы компильнем cython, чтобы было еще быстрее
Modern? Modern!
Осталось дождаться. Релиз скоро!
И еще скоро будут несколько новых видео.
Мы регулярно постим задачки в @opensource_findings_python, так что можно нам помочь.
А если кто хочет мне закинуть на развитие опенсорса (за написание 32500 строк кода за 2 недели), то мой бусти открыт: https://boosty.to/sobolevn
Ну и звезды можно ставить, конечно же. Там посмотрите, какие люди нас уже советуют.
Большое спасибо Александру, Алексею, Роману, Максиму и всем остальным за неоценимую помощь. Пацаны, без вас никак! Лучшее сообщество!
Обсуждение: а чего бы вам хотелось в modern rest фреймворке? Чего сильно не хватает сейчас?
77👍195🔥77❤63💩6😁2
Находки в опенсорсе
minimal vscode: убираем вкладки https://www.youtube.com/watch?v=reT_wnDSaX4 Вкладки любят делать вид, что они очень полезны. Однако, такое впечатление обманчиво. Навигация по ним будет съедать у вас кучу времени. Взамен – есть способы лучше. Что будет в…
minimal vscode: убираем / кастомизируем status bar
https://www.youtube.com/watch?v=H5iVQZNk92s
В статусбаре в vscode – очень много всего: как полезного, так и лишнего.
Сегодня посмотрим, как можно его кастомизировать:
- Можно просто спрятать (как делаю я)
- Можно очень детально кастомизировать
- Можно перенести часть его функций в плагины
И вот тут главная фича vscode выходит наружу – у нас ведь просто браузер с html / css / js.
А значит, мы можем использовать css и js для кастомизации всего!
И оно будет работать одинаково даже в cloud версиях IDE.
В выпуске:
- Будем писать CSS для удаления лишнего из status bar
- И создавать локальные плагины на #javascript для кастомизации поведения редактора
Впереди еще пара взрывающих мозг видосов, будем и дальше превращать vscode в Черный Квадрат Малевича.
В следующих видео:
- Уберем оставшиеся части UI
- Сделаем свою тему, нативная интеграция самой минималистичной темной темы от меня (есть для vscode и nvim): https://github.com/pustota-theme/pustota
- Поговорим про git
- Научимся пользоваться дебагером, как будто мы трухацкеры
Обсуждение: То что vscode - браузер - плюс или минус лично для вас? И почему?
| Поддержать | YouTube | GitHub | Чат |
https://www.youtube.com/watch?v=H5iVQZNk92s
В статусбаре в vscode – очень много всего: как полезного, так и лишнего.
Сегодня посмотрим, как можно его кастомизировать:
- Можно просто спрятать (как делаю я)
- Можно очень детально кастомизировать
- Можно перенести часть его функций в плагины
И вот тут главная фича vscode выходит наружу – у нас ведь просто браузер с html / css / js.
А значит, мы можем использовать css и js для кастомизации всего!
И оно будет работать одинаково даже в cloud версиях IDE.
В выпуске:
- Будем писать CSS для удаления лишнего из status bar
"custom-ui-style.stylesheet": {
"#status\\.problems": {
"visibility": "hidden !important",
"display": "none !important",
}
}
- И создавать локальные плагины на #javascript для кастомизации поведения редактора
function updateColumnPosition() {
const positionLabel = document.querySelector(
'#status\\.editor\\.selection .statusbar-item-label',
)
if (!positionLabel || !positionLabel.textContent) {
// It might not exist for some reason ¯\_(ツ)_/¯
return
}
const currentLine = document.querySelector(
'.line-numbers.active-line-number',
)
// Now we would have the column position instead of the active line number:
const colNumber = positionLabel.textContent.match(/Col (\d+)/)
currentLine.textContent = colNumber[1]
}
Впереди еще пара взрывающих мозг видосов, будем и дальше превращать vscode в Черный Квадрат Малевича.
В следующих видео:
- Уберем оставшиеся части UI
- Сделаем свою тему, нативная интеграция самой минималистичной темной темы от меня (есть для vscode и nvim): https://github.com/pustota-theme/pustota
- Поговорим про git
- Научимся пользоваться дебагером, как будто мы трухацкеры
Обсуждение: То что vscode - браузер - плюс или минус лично для вас? И почему?
| Поддержать | YouTube | GitHub | Чат |
YouTube
Гайд по кастомизации vscode: чистим status bar, пишем плагины на #javascript
– Мой телеграм канал: https://xn--r1a.website/opensource_findings
– Поддержать: https://boosty.to/sobolevn
– Мой GitHub: https://github.com/sobolevn
– Прислать материал для видео: https://xn--r1a.website/opensource_findings_chat
Максимальная продуктивность и минимализм в visual…
– Поддержать: https://boosty.to/sobolevn
– Мой GitHub: https://github.com/sobolevn
– Прислать материал для видео: https://xn--r1a.website/opensource_findings_chat
Максимальная продуктивность и минимализм в visual…
3🔥70❤16👍16🤔2🤡1
Breaking news
В CPython предлагают добавить Rust: https://discuss.python.org/t/pre-pep-rust-for-cpython/104906
Пример кода: https://github.com/emmatyping/cpython/pull/13/files
Все подробности будут в @cpython_notes
Обсуждение: что думаете?
| Поддержать | YouTube | GitHub | Чат |
В CPython предлагают добавить Rust: https://discuss.python.org/t/pre-pep-rust-for-cpython/104906
Пример кода: https://github.com/emmatyping/cpython/pull/13/files
#[unsafe(no_mangle)]
pub unsafe extern "C" fn b64encode(
_module: *mut PyObject,
args: *mut *mut PyObject,
nargs: Py_ssize_t,
) -> *mut PyObject {
if nargs != 1 {
unsafe {
PyErr_SetString(
PyExc_TypeError,
c"b64encode() takes exactly one argument".as_ptr(),
);
}
return ptr::null_mut();
}
let source = unsafe { *args };
let buffer = match unsafe { BorrowedBuffer::from_object(source) } {
Ok(buf) => buf,
Err(_) => return ptr::null_mut(),
};
let view_len = buffer.len();
if view_len < 0 {
unsafe {
PyErr_SetString(
PyExc_TypeError,
c"b64encode() argument has negative length".as_ptr(),
);
}
return ptr::null_mut();
}
let input_len = view_len as usize;
let input = unsafe { slice::from_raw_parts(buffer.as_ptr(), input_len) };
let Some(output_len) = encoded_output_len(input_len) else {
unsafe {
PyErr_NoMemory();
}
return ptr::null_mut();
};
if output_len > isize::MAX as usize {
unsafe {
PyErr_NoMemory();
}
return ptr::null_mut();
}
let result = unsafe {
PyBytes_FromStringAndSize(ptr::null(), output_len as Py_ssize_t)
};
if result.is_null() {
return ptr::null_mut();
}
let dest_ptr = unsafe { PyBytes_AsString(result) };
if dest_ptr.is_null() {
unsafe {
Py_DecRef(result);
}
return ptr::null_mut();
}
let dest = unsafe { slice::from_raw_parts_mut(dest_ptr.cast::<u8>(), output_len) };
let written = encode_into(input, dest);
debug_assert_eq!(written, output_len);
result
}
Все подробности будут в @cpython_notes
Обсуждение: что думаете?
| Поддержать | YouTube | GitHub | Чат |
Discussions on Python.org
Pre-PEP: Rust for CPython
Introduction We (@emmatyping, @eclips4) propose introducing the Rust programming language to CPython. Rust will initially only be allowed for writing optional extension modules, but eventually will become a required dependency of CPython and allowed to be…
❤49👍36🔥31🤡20🤯15😁12👎8😱6🤔5💩5👌2
Находки в опенсорсе
Аллокаторы в СPython: PyArena Один из самых простых аллокаторов в питоне. Исходники. По сути данный аллокатор является небольшой оберткой поверх PyMem_Malloc, но с интересной особенностью. Если PyMem_Malloc имеет PyMem_Free для освобождения памяти каждого…
Аллокаторы в СPython: база
Тема аллокаторов иногда питонистам кажется сложной, потому что в питоне мы их не вызываем явно. Оттого с ними не очень знакомы, так давайте исправлять и знакомиться!
Зачем вообще нужно много разных аллокаторов? Все они делают одно и то же: выделяют память в куче (heap). В зависимости от наших вариантов использования данной памяти - выделять и освобождать её нужно очень по-разному.
Где-то множество мелких объектов, которые часто создаются и очищаются. Где-то несколько больших, которые должны умирать все вместе. Где-то мы работаем в рамках одного потока, где-то несколько потоков будут запрашивать / высвобождать память параллельно.
Например: при парсинге AST мы используем PyArena аллокатор. Он выделяет сразу много памяти, сразу вычищает все за один раз. Что идеально подходит для парсинга.
Но, для рантайма - задачи, конечно же другие. Там есть долгоживущие объекты, есть много мелких краткоживущих, есть довольно большие, есть маленькие. Для таких задач используют "general purpose allocators". Которые в среднем хороши во всем.
Дизайн аллокаторов в CPython
Питон знает, как его будут использовать. Потому поверх базовых GPA есть собственные надстройки.
Документация:
- https://docs.python.org/3/c-api/allocation.html
- https://docs.python.org/3/c-api/memory.html
В CPython есть: malloc, pymalloc, mimalloc и некоторые их варианты для дебага.
Они разделены на три "домена" для аллокаторов, то с чем они работают, какие задачи решают:
-
-
-
Разработчики C-extensions должны понимать, когда какой использовать и под какие задачи.
К счастью, разработчикам на питоне - такое нужно только для любопытства.
А вот таблица, какие реальные аллокаторы используют те или иные C-API функции в разных режимах:
Она правда немного устарела и не отражает Free-Threading сборки, которые требуют mimalloc 🌚
Кто первый успеет сделать PR с исправлением - тот молодец!
О
Зачем питону свой аллокатор?
В CPython есть (был? для free-threading он не используется и не будет) свой аллокатор: pymalloc, основная задача которого – работа с маленькими Python объектами.
Про него полностью тоже нужно писать большой отдельный пост.
Что вообще важно в аллокаторе?
- Стратегия выделения памяти под новый запрос
- Работа с округлениями размера памяти и выравнивание
- Дефрагментация памяти
- Стратегия очистки памяти
Но кратко про
- Он создает арены по 1MB
- Внутри арены разделены на пулы по 16KB
- Внутри пулы поделены на блоки фиксированного размера
Зачем? Чтобы не аллоцировать часто маленькие кусочки памяти. Что дорого.
Можно ли управлять аллокаторами?
Да! Есть опции для сборки:
И даже переменная окружения PYTHONMALLOC, которая позволяет указать, какой аллокатор использовать для всех случаев. Зачем? Прежде всего для дебага. Но можно потестить, вдруг будет давать буст по скорости или потреблению памяти в ваших вариантах использования.
Обсуждение: какой ваш любимый аллокатор? И почему jemalloc?
| Поддержать | YouTube | GitHub | Чат |
Тема аллокаторов иногда питонистам кажется сложной, потому что в питоне мы их не вызываем явно. Оттого с ними не очень знакомы, так давайте исправлять и знакомиться!
Зачем вообще нужно много разных аллокаторов? Все они делают одно и то же: выделяют память в куче (heap). В зависимости от наших вариантов использования данной памяти - выделять и освобождать её нужно очень по-разному.
Где-то множество мелких объектов, которые часто создаются и очищаются. Где-то несколько больших, которые должны умирать все вместе. Где-то мы работаем в рамках одного потока, где-то несколько потоков будут запрашивать / высвобождать память параллельно.
Например: при парсинге AST мы используем PyArena аллокатор. Он выделяет сразу много памяти, сразу вычищает все за один раз. Что идеально подходит для парсинга.
Но, для рантайма - задачи, конечно же другие. Там есть долгоживущие объекты, есть много мелких краткоживущих, есть довольно большие, есть маленькие. Для таких задач используют "general purpose allocators". Которые в среднем хороши во всем.
Дизайн аллокаторов в CPython
Питон знает, как его будут использовать. Потому поверх базовых GPA есть собственные надстройки.
Документация:
- https://docs.python.org/3/c-api/allocation.html
- https://docs.python.org/3/c-api/memory.html
В CPython есть: malloc, pymalloc, mimalloc и некоторые их варианты для дебага.
Они разделены на три "домена" для аллокаторов, то с чем они работают, какие задачи решают:
-
Raw: для выделения памяти для общих задач, например под сишные буферы или IO. Может работать без PyThreadState-
Mem: для выделения памяти для общих задач, но уже с PyThreadState, например под Python буферы, подходит для мелких объектов-
Object: для выделения памяти под конкретные мелкие объектыРазработчики C-extensions должны понимать, когда какой использовать и под какие задачи.
К счастью, разработчикам на питоне - такое нужно только для любопытства.
А вот таблица, какие реальные аллокаторы используют те или иные C-API функции в разных режимах:
PyMem_RawMalloc -> malloc
PyMem_Malloc -> pymalloc
PyObject_Malloc -> pymalloc
Она правда немного устарела и не отражает Free-Threading сборки, которые требуют mimalloc 🌚
Кто первый успеет сделать PR с исправлением - тот молодец!
О
mimalloc мы как-нибудь отдельно поговорим, там нужно рассказывать сильно глубже, в том числе про GC и PyGC_Head.Зачем питону свой аллокатор?
В CPython есть (был? для free-threading он не используется и не будет) свой аллокатор: pymalloc, основная задача которого – работа с маленькими Python объектами.
Про него полностью тоже нужно писать большой отдельный пост.
Что вообще важно в аллокаторе?
- Стратегия выделения памяти под новый запрос
- Работа с округлениями размера памяти и выравнивание
- Дефрагментация памяти
- Стратегия очистки памяти
struct arena_object {
uintptr_t address;
pymem_block* pool_address;
uint nfreepools;
uint ntotalpools;
struct pool_header* freepools;
struct arena_object* nextarena;
struct arena_object* prevarena;
};
Но кратко про
pymalloc можно сказать следующее:- Он создает арены по 1MB
- Внутри арены разделены на пулы по 16KB
- Внутри пулы поделены на блоки фиксированного размера
Зачем? Чтобы не аллоцировать часто маленькие кусочки памяти. Что дорого.
Можно ли управлять аллокаторами?
Да! Есть опции для сборки:
--without-mimalloc, --without-pymallocИ даже переменная окружения PYTHONMALLOC, которая позволяет указать, какой аллокатор использовать для всех случаев. Зачем? Прежде всего для дебага. Но можно потестить, вдруг будет давать буст по скорости или потреблению памяти в ваших вариантах использования.
Обсуждение: какой ваш любимый аллокатор? И почему jemalloc?
| Поддержать | YouTube | GitHub | Чат |
Python documentation
Allocating Objects on the Heap
Deprecated aliases: These are soft deprecated aliases to existing functions and macros. They exist solely for backwards compatibility.,, Deprecated alias, Function,,,, PyObject_New,,, PyObject_NewV...
1🔥75👍36❤11👏2👌1🕊1
git-lfs: храним большие файлы в репозитории правильно
https://www.youtube.com/watch?v=82wj6y2rmR4
Вы сталкивались с проблемой, что рабочий проект клонируется 10 минут?
А когда начинаешь разбираться: почему так? То оказывается, что внутри десятки непережатых картинок для фронта, которые еще и менялись регулярно (а значит, оставили след в истории git навсегда).
Данная проблема влияет не только на локальное использование, ведь мы на самом деле довольно редко делаем
Решение: использовать git-lfs!
Я пригласил замечательного Олега Чирухина @tg_1red2black, чтобы обсудить:
- Как работает git-lfs на базовом уровне?
- Как мигрировать на него с базового сетапа?
- Как он устроен внутри? Поднимаем https://github.com/git-lfs/lfs-test-server и детально смотрим, что там внутри происходит
Ну и конечно чуть-чуть глянули исходники, они, кстати, на #go 🌚️️️️
Обсуждение: как вы храните большие файлы в рабочих проектах? Насколько большие файлы вы храните?
| Поддержать | YouTube | GitHub | Чат |
https://www.youtube.com/watch?v=82wj6y2rmR4
Вы сталкивались с проблемой, что рабочий проект клонируется 10 минут?
А когда начинаешь разбираться: почему так? То оказывается, что внутри десятки непережатых картинок для фронта, которые еще и менялись регулярно (а значит, оставили след в истории git навсегда).
Данная проблема влияет не только на локальное использование, ведь мы на самом деле довольно редко делаем
git clone с нуля, но и самое главное – на скорость всех наших сборок (если мы не используем fetch-depth: 1 или аналог, а использовать их надо). Решение: использовать git-lfs!
Я пригласил замечательного Олега Чирухина @tg_1red2black, чтобы обсудить:
- Как работает git-lfs на базовом уровне?
- Как мигрировать на него с базового сетапа?
- Как он устроен внутри? Поднимаем https://github.com/git-lfs/lfs-test-server и детально смотрим, что там внутри происходит
Ну и конечно чуть-чуть глянули исходники, они, кстати, на #go 🌚️️️️
Обсуждение: как вы храните большие файлы в рабочих проектах? Насколько большие файлы вы храните?
| Поддержать | YouTube | GitHub | Чат |
YouTube
Находки в опенсорсе: git-lfs, не засоряй репозиторий большими файлами зря! #git
GigaCode – AI-ассистент разработчика c агентным режимом. Это полноценный помощник разработчика, способный понимать контекст проекта и выполнять задачи от анализа до готового решения. Ассистент сам открывает нужные файлы, вносит изменения, запускает тесты…
52🔥58👍22❤10👏1
PEP-799: Семплирующий профилировщик встроенный в Python 3.15+
Краткий обзор: https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-sampling-profiler
Документация: https://docs.python.org/3.15/library/profiling.sampling.html
Кратко:
- В 3.15
- В 3.15
- Добавили новый
- А еще добавили встроенные TUI (на скриншоте) и GUI
Ключевые фичи:
- Almost Zero-overhead: добиваемся такого, за счет того, что переодически получаем готовые стектрейсы работающих процессов, не нужно добавлять инструментацию функций как в
- Не нужно менять существующий код
- Разные режимы работы:
- Разные виды замеров:
- Поддержка тредов
- Поддержка asyncio с флагом
- Разные форматы вывода информации:
-
- Поддержка профилирования до уровня опкодов виртуальной машины, включая специализации
- Гибкая конфигурация буквально всего
Примеры запуска:
-
-
На чем основана работа технически?
- PEP-768 с remote-debugging предоставляет техническую возможность быстро и легко получать семплы из виртуальной машины
- Для asyncio используется новый АПИ для async-aware call-graphs
Личное мнение: очень крутая фича, пока у меня вопросы по TUI / GUI. Не очень понимаю, зачем их затащили в stdlib. Зарепортил пару багов. На маке требуется запускать профилировщик с
Кирилл вот тоже заценил.
Обсуждение: Что вы думаете о данной фиче? Как вы сейчас профилируете ваши приложения в разработке и в проде?
P.S. Последний пост в текущем году. Всех с наступающим! 🎄
| Поддержать | YouTube | GitHub | Чат |
Краткий обзор: https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-sampling-profiler
Документация: https://docs.python.org/3.15/library/profiling.sampling.html
Кратко:
- В 3.15
cProfile (написан на C) был перемещен в profiling.tracing- В 3.15
profile (написан на Python) стал deprecated, его уберут в 3.17- Добавили новый
profiling.sampling (кодовое имя `tachyons`), о нем и поговорим- А еще добавили встроенные TUI (на скриншоте) и GUI
Ключевые фичи:
- Almost Zero-overhead: добиваемся такого, за счет того, что переодически получаем готовые стектрейсы работающих процессов, не нужно добавлять инструментацию функций как в
cProfile- Не нужно менять существующий код
- Разные режимы работы:
attach позволяет подключиться к работающему Python процессу, а run позволяет запустить код для профилировки- Разные виды замеров:
--mode wall, --mode cpu, --mode gil, --mode exception- Поддержка тредов
- Поддержка asyncio с флагом
--async-aware, можно смотреть даже suspended tasks с --async-mode=all- Разные форматы вывода информации:
--flamegraph, --pstats, --heatmap, --gecko-
--live вместе с TUI для отображения информации в реальном времени (как в top условном)- Поддержка профилирования до уровня опкодов виртуальной машины, включая специализации
- Гибкая конфигурация буквально всего
Примеры запуска:
-
python -m profiling.sampling run --flamegraph -o profile.html script.py - запускаем скрипты и генерим флеймграф-
python -m profiling.sampling attach --live $YOUR_PID - профилируем работающий процесс и получаем данные в реальном времениНа чем основана работа технически?
- PEP-768 с remote-debugging предоставляет техническую возможность быстро и легко получать семплы из виртуальной машины
- Для asyncio используется новый АПИ для async-aware call-graphs
Личное мнение: очень крутая фича, пока у меня вопросы по TUI / GUI. Не очень понимаю, зачем их затащили в stdlib. Зарепортил пару багов. На маке требуется запускать профилировщик с
sudo -E. Остальное - нравится!Кирилл вот тоже заценил.
Обсуждение: Что вы думаете о данной фиче? Как вы сейчас профилируете ваши приложения в разработке и в проде?
P.S. Последний пост в текущем году. Всех с наступающим! 🎄
| Поддержать | YouTube | GitHub | Чат |
2👍107🔥75❤11🎉4
PEP-800: typing.disjoint_base
ПЕП: https://peps.python.org/pep-0800
Обсуждение: https://discuss.python.org/t/99910
Реализация в
Когда я критикую систему типов в питоне, говоря, что её не продумывали заранее, то
В питоне можно наследоваться от нескольких классов (и вообще всего с методом
Например:
Почему такое происходит почитать можно тут. Но, нам важно узнать, что в CPython есть концепция "solid base", которая считается для всех созданных классов вот тут. Если очень кратко, то CPython должен уметь построить правильный memory-layout для всех классов потомков. Например, все потомки
Иначе - перестанет работать базовая логика
Сделать область памяти для потомка
Но, сделать так можно и со своими классами, не обязательно использовать C, достаточно конфликта в
Подробности из ПЕПа про "solid bases".
Типизация
Теперь
Как следствие, тайпчекеры теперь более четко смогут находить и другие проблемы. Например, в местах где мы создаем "временный" тип:
Раньше такой код проходил в некоторых тайпчекерах. Ведь они думали, что тип подкласс
Отличный ПЕП, система типов стала чуть лучше.
Обсуждение: знали ли вы про solid и disjoint bases в питоне? Стреляли ли себе в ногу таким?
| Поддержать | YouTube | GitHub | Чат |
ПЕП: https://peps.python.org/pep-0800
Обсуждение: https://discuss.python.org/t/99910
Реализация в
typing_extensions: https://github.com/python/typing_extensions/blob/a7610ef567132cac2b5319fa193c830b655364c6/src/typing_extensions.py#L358Когда я критикую систему типов в питоне, говоря, что её не продумывали заранее, то
disjoint_base - отличный пример моих слов.В питоне можно наследоваться от нескольких классов (и вообще всего с методом
__mro_entries__, да). Что иногда довольно удобно, если использовать без фанатизма. Но проблема в том, что не все наборы классов подходят для множественного наследования.Например:
>>> class What(str, int): ...
Traceback (most recent call last):
TypeError: multiple bases have instance lay-out conflict
Почему такое происходит почитать можно тут. Но, нам важно узнать, что в CPython есть концепция "solid base", которая считается для всех созданных классов вот тут. Если очень кратко, то CPython должен уметь построить правильный memory-layout для всех классов потомков. Например, все потомки
int должны быть вот так уложены в памяти:
typedef struct _PyLongValue {
uintptr_t lv_tag; /* Number of digits, sign and flags */
digit ob_digit[1];
} _PyLongValue;
struct _longobject {
PyObject_HEAD
_PyLongValue long_value;
};
Иначе - перестанет работать базовая логика
int. А str устроены по-другому.
/* Object format for Unicode subclasses. */
typedef struct {
PyCompactUnicodeObject _base;
union {
void *any;
Py_UCS1 *latin1;
Py_UCS2 *ucs2;
Py_UCS4 *ucs4;
} data; /* Canonical, smallest-form Unicode buffer */
} PyUnicodeObject;
Сделать область памяти для потомка
int и str сразу - невозможно. Потому и выкидывается ошибка.Но, сделать так можно и со своими классами, не обязательно использовать C, достаточно конфликта в
__slots__:
>>> class A:
... __slots__ = ('a',)
>>> class B:
... __slots__ = ('b',)
>>> class C(A, B): ...
Traceback (most recent call last):
TypeError: multiple bases have instance lay-out conflict
Подробности из ПЕПа про "solid bases".
Типизация
Теперь
int, str и многие другие классы в typeshed помечены как @disjoint_base типы. Значит, что только один такой класс может быть в mro. Раньше такое костылили все тайпчекеры по-своему.Как следствие, тайпчекеры теперь более четко смогут находить и другие проблемы. Например, в местах где мы создаем "временный" тип:
def g(x: int):
match x:
case str(): # unreachable
print("It's both!")
Раньше такой код проходил в некоторых тайпчекерах. Ведь они думали, что тип подкласс
int и str может существовать. А теперь - будут знать, что такое невозможно на уровне определения и выкидывать правильную ошибку.Отличный ПЕП, система типов стала чуть лучше.
Обсуждение: знали ли вы про solid и disjoint bases в питоне? Стреляли ли себе в ногу таким?
| Поддержать | YouTube | GitHub | Чат |
Python Enhancement Proposals (PEPs)
PEP 800 – Disjoint bases in the type system | peps.python.org
To analyze Python programs precisely, type checkers need to know when two classes can and cannot have a common child class. However, the information necessary to determine this is not currently part of the type system. This PEP adds a new decorator, @ty...
4🤔61👍58❤30🔥17🤯6🤡3💩2😱1🕊1
Обстановка в опенсорсе прямо сейчас: https://github.com/wemake-services/wemake-python-styleguide/issues/3596
А пока - все вместе ждем релиза
А пока - все вместе ждем релиза
django_modern_rest, уже скоро.3😁226🤡37❤13🔥8👏6🤔6😢5
PEP-814: frozendict
В Python 3.15 появится полноценный иммутабельный словарь.
PEP: https://peps.python.org/pep-0814
Обсуждение: https://discuss.python.org/t/pep-814-add-frozendict-built-in-type/104854
Оригинальный PR: https://github.com/python/cpython/pull/144757
Исходники (да, они с
Зачем?
Главный вопрос: зачем питону вдруг через 35 лет понадобился иммутабельный словарь? Мотивации в ПЕПе явно не очень хватает. Но я докину:
1.
2. С иммутабельными объектами куда проще работать в режиме Free-Threading
3. Многие другие новые идеи вроде Виртуальных Потоков тоже хотели бы иметь аналог иммутабельного словаря
Ну а
И вот у нас появилась точная копия обычного
Примеры
Но не умеет ничего из
Зато умеет в
Как его менять? А вот так, создавая новые:
Детали реализации
Чтобы вы понимали, насколько они похожи:
Интересно, как работает
В C-API тоже добавили функций для работы с новым словарем:
А еще половина stdlib поменяет константы с
Отличный ПЕП, простая реализация, крутая фича. Питон победа!
Обсуждение: как вы относитесь к иммутабельности в питоне и вообще?
| Поддержать | YouTube | GitHub | Чат |
В Python 3.15 появится полноценный иммутабельный словарь.
PEP: https://peps.python.org/pep-0814
Обсуждение: https://discuss.python.org/t/pep-814-add-frozendict-built-in-type/104854
Оригинальный PR: https://github.com/python/cpython/pull/144757
Исходники (да, они с
dict лежат в одном файле на 8к строк)Зачем?
Главный вопрос: зачем питону вдруг через 35 лет понадобился иммутабельный словарь? Мотивации в ПЕПе явно не очень хватает. Но я докину:
1.
frozendict можно будет шарить между разными интерпретаторами без какого-либо оверхеда2. С иммутабельными объектами куда проще работать в режиме Free-Threading
3. Многие другие новые идеи вроде Виртуальных Потоков тоже хотели бы иметь аналог иммутабельного словаря
Ну а
types.MappingProxyType(mapping) был только surface-immutable. Все равно можно было поменять оригинальный объект mapping.И вот у нас появилась точная копия обычного
dict, только иммутабельная:Примеры
frozendict является collections.abc.Mapping и таким же Generic с двумя параметрами:
>>> frozendict.__mro__
(<class 'frozendict'>, <class 'object'>)
>>> obj = frozendict({'a': 1})
>>> frozendict[str, int]
frozendict[str, int]
Но не умеет ничего из
collections.abc.MutableMapping:
>>> obj['a'] = 2
TypeError: 'frozendict' object does not support item assignment
>>> obj.update
AttributeError: 'frozendict' object has no attribute 'update'
Зато умеет в
hash, если все ключи и значения умеют в hash:
>>> hash(obj)
6343282633043897990
>>> hash(frozendict({1: []}))
TypeError: unhashable type: 'list'
Как его менять? А вот так, создавая новые:
>>> obj = frozendict({'a': 1})
>>> id(obj)
4352339472
>>> obj |= {'b': 2} # <- тут мы создали новый frozendict
>>> obj
frozendict({'a': 1, 'b': 2})
>>> id(obj)
4352341680
Детали реализации
Чтобы вы понимали, насколько они похожи:
frozendict просто переиспользует clinic макросы dict для определения своих методов (=использует те же методы):
static PyMethodDef frozendict_methods[] = {
DICT___CONTAINS___METHODDEF
{"__getitem__", dict_subscript, METH_O | METH_COEXIST, getitem__doc__},
DICT___SIZEOF___METHODDEF
DICT_GET_METHODDEF
DICT_KEYS_METHODDEF
DICT_ITEMS_METHODDEF
DICT_VALUES_METHODDEF
DICT_FROMKEYS_METHODDEF
DICT_COPY_METHODDEF
DICT___REVERSED___METHODDEF
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
{NULL, NULL} /* sentinel */
};
Интересно, как работает
hash: он полностью дублирует алгоритм хеша из frozenset.В C-API тоже добавили функций для работы с новым словарем:
PyFrozenDict_New, PyAnyDict_Check проверяет на dict, frozendict или их подтипы.А еще половина stdlib поменяет константы с
dict на frozendict.Отличный ПЕП, простая реализация, крутая фича. Питон победа!
Обсуждение: как вы относитесь к иммутабельности в питоне и вообще?
| Поддержать | YouTube | GitHub | Чат |
Python Enhancement Proposals (PEPs)
PEP 814 – Add frozendict built-in type | peps.python.org
A new public immutable type frozendict is added to the builtins module.
3👍169🔥45❤11🎉5😢2🥰1😁1👌1
PEP-747: TypeForm, или "аннотируем аннотации"
PEP: https://peps.python.org/747
Реализация: https://github.com/python/cpython/pull/145034
Что и зачем?
Представьте, что вам нужно описать, что какая-то функция может принять в качестве входного аргумента любую аннотацию. Например для валидации как в пидантике. Как бы вы такое сделали?
Но, на самом деле у нас тут есть ошибка.
Например,
Пример в pyright.
Данная фича реально нужна авторам библиотек, кто строит свою логику работы на типах.
Реальный пример: в django-modern-rest (нативная интеграция 🌚️️️️) мы создаем метаданные об ответе
На данный момент
Но в идеале мы будем использовать
Как и многие другие:
• pydantic
• msgspec
• dishka (использует аннотации для DI)
• Даже некоторые места stdlib, например: dataclasses.fields
Итоговый пример:
Текущее состояние
В
В pyright поддержка уже есть полтора года как.
В Python3.15 будет нативно в
Еще одна хорошая фича.
Обсуждение: какие варианты использования
| Поддержать | YouTube | GitHub | Чат |
PEP: https://peps.python.org/747
Реализация: https://github.com/python/cpython/pull/145034
Что и зачем?
Представьте, что вам нужно описать, что какая-то функция может принять в качестве входного аргумента любую аннотацию. Например для валидации как в пидантике. Как бы вы такое сделали?
type[T]?
from typing import Any
def validate[T](typ: type[T], value: Any) -> None: ...
validate(int, 1)
Но, на самом деле у нас тут есть ошибка.
typ будет ожидать любой объект класса type, а не любую аннотацию.Например,
int | str не является объектом класса type, но является валидной аннотацией. Так же как и: None, Literal[1], Self, T, тд.Пример в pyright.
Данная фича реально нужна авторам библиотек, кто строит свою логику работы на типах.
Реальный пример: в django-modern-rest (нативная интеграция 🌚️️️️) мы создаем метаданные об ответе
ResponseSpec(return_type=int | str, status_code=200) для такого кода пользователя:
from dmr import Controller
from dmr.plugins.msgspec import MsgspecSerializer
class UserController(Controller[MsgspecSerializer]):
async def get(self) -> int | str: ...
На данный момент
ResponseSpec.return_type имеет аннотацию Any, как единственный рабочий вариант. Но в идеале мы будем использовать
TypeForm[Any] в ближайшее время.Как и многие другие:
• pydantic
• msgspec
• dishka (использует аннотации для DI)
• Даже некоторые места stdlib, например: dataclasses.fields
Итоговый пример:
from typing_extensions import TypeForm, Any
def validate[T](typ: TypeForm[T], value: Any) -> T: ...
reveal_type(validate(int, 1)) # int
reveal_type(validate(int | str, 1)) # int | str
Текущее состояние
В
typing_extensions код уже есть, поддержка в mypy будет в версии 1.20 (следующей).В pyright поддержка уже есть полтора года как.
В Python3.15 будет нативно в
typing. Еще одна хорошая фича.
Обсуждение: какие варианты использования
TypeForm есть у вас? | Поддержать | YouTube | GitHub | Чат |
Python Enhancement Proposals (PEPs)
PEP 747 – Annotating Type Forms | peps.python.org
Type expressions provide a standardized way to specify types in the Python type system. When a type expression is evaluated at runtime, the resulting type form object encodes the information supplied in the type expression. This enables a variety of use...
2❤68🔥26👍22💩4👌1
PEP-827: Самое интересное, что случалось с типами в питоне!
Текст: https://peps.python.org/pep-0827/
Обсуждение: https://discuss.python.org/t/pep-827-type-manipulation/106353
Если вы когда-то писали на TypeScript (одобряем) или на каких-то других языках с продвинутой системой типов, вам всегда должно было быть больно от того, что происходит в Python.
Да, тут можно выразить некоторые простые вещи. Но, как например типизировать такой код?
Никак, обидно. Я даже 100 лет назад делал такую поделку: https://github.com/wemake-services/mypy-extras
Чтобы хоть как-то решать проблему выше.
Предложение
И вот Юрий Селиванов (автор asyncio и edge-db) предлагает добавить в питон специальные действия над типами.
Чтобы было как в TS, где есть условные и рекурсивные типы, готовые операторы как
Вот что предлагают добавить:
А еще:
– Типовые операторы:
– Методы для интроспекции объектов в типах:
– Создание типов внутри аннотаций:
Пример
Показать детали работы всего я, конечно, не смогу. Но смогу показать один пример из ПЕПа.
Понятная проблема: есть какая-то модель пользователя. При создании данной модели - мы указываем все поля, кроме
Сейчас мы делаем что-то типа
Но, мы можем создавать такие модели при помощи типов.
Полный код: https://github.com/vercel/python-typemap/blob/main/tests/test_fastapilike_2.py
Данная страшная конструкция будет спрятана внутри
А внутри уже:
– Полная типизация всех полей
– Новая корректная модель, которая всегда актуальна
Круто?
Мое мнение: в детали данного предложения я пока не вникал, но в целом - направление правильное.
Обсуждение: а что вы думаете про такое развитие типизации в питоне?
P.S. Из телеги и ютюба не перекатываемся. Рекламы на канале и так почти не было, для меня - мало что меняется.
Если вы хотите поддерживать мою работу в опенсорсе и контент без рекламы скам-курсов и вечных прогревов, то всегда можно закинуть на бусти: https://boosty.to/sobolevn
| Поддержать | YouTube | GitHub | Чат |
Текст: https://peps.python.org/pep-0827/
Обсуждение: https://discuss.python.org/t/pep-827-type-manipulation/106353
Если вы когда-то писали на TypeScript (одобряем) или на каких-то других языках с продвинутой системой типов, вам всегда должно было быть больно от того, что происходит в Python.
Да, тут можно выразить некоторые простые вещи. Но, как например типизировать такой код?
@dataclass
class User:
username: str
age: int
def get_field(obj: Any, field_name: str) -> Any:
return getattr(obj, field_name)
user = User('example', 18)
username = get_field(user, 'username')
# ^ мы знаем, что тут `str`, но никак не можем такое выразить, кроме КУЧИ `@overload` для конкретного типа
# а для общего случая - вообще никак
Никак, обидно. Я даже 100 лет назад делал такую поделку: https://github.com/wemake-services/mypy-extras
Чтобы хоть как-то решать проблему выше.
Предложение
И вот Юрий Селиванов (автор asyncio и edge-db) предлагает добавить в питон специальные действия над типами.
Чтобы было как в TS, где есть условные и рекурсивные типы, готовые операторы как
keyof и куча дополнительных типов в npm.Вот что предлагают добавить:
<type> = ...
# Type booleans are all valid types too
| <type-bool>
# Conditional types
| <type> if <type-bool> else <type>
# Types with variadic arguments can have
# *[... for t in ...] arguments
| <ident>[<variadic-type-arg> +]
# Type member access
| <type>.<name>
| GenericCallable[<type>, lambda <args>: <type>]
А еще:
– Типовые операторы:
IsAssignable, IsEquivalent, GetArg, FromUnion, тд– Методы для интроспекции объектов в типах:
Members, Attrs, GetMember, тд– Создание типов внутри аннотаций:
NewProtocol, NewTypedDictПример
Показать детали работы всего я, конечно, не смогу. Но смогу показать один пример из ПЕПа.
Понятная проблема: есть какая-то модель пользователя. При создании данной модели - мы указываем все поля, кроме
primary_key. Но показывать мы будем наружу все поля, кроме password.Сейчас мы делаем что-то типа
class UserBase(SQLModel):
name: str = Field(index=True)
age: int | None = Field(default=None, index=True)
class User(UserBase, table=True):
id: int | None = Field(default=None, primary_key=True)
password: str = Field(hidden=True)
class UserPublic(UserBase):
id: int
class UserCreate(UserBase):
password: str
Но, мы можем создавать такие модели при помощи типов.
Полный код: https://github.com/vercel/python-typemap/blob/main/tests/test_fastapilike_2.py
# Extract the default type from an Init field.
# If it is a Field, then we try pulling out the "default" field,
# otherwise we return the type itself.
type GetDefault[Init] = (
GetFieldItem[Init, Literal["default"]]
if typing.IsAssignable[Init, Field]
else Init
)
# Create takes everything but the primary key and preserves defaults
type Create[T] = typing.NewProtocol[
*[
typing.Member[
p.name,
p.type,
p.quals,
GetDefault[p.init],
]
for p in typing.Iter[typing.Attrs[T]]
if not typing.IsAssignable[
Literal[True],
GetFieldItem[p.init, Literal["primary_key"]],
]
]
]
Данная страшная конструкция будет спрятана внутри
SQLModel, а мы будем писать просто:
UserCreate = Create[User]
А внутри уже:
– Полная типизация всех полей
– Новая корректная модель, которая всегда актуальна
Круто?
Мое мнение: в детали данного предложения я пока не вникал, но в целом - направление правильное.
Обсуждение: а что вы думаете про такое развитие типизации в питоне?
P.S. Из телеги и ютюба не перекатываемся. Рекламы на канале и так почти не было, для меня - мало что меняется.
Если вы хотите поддерживать мою работу в опенсорсе и контент без рекламы скам-курсов и вечных прогревов, то всегда можно закинуть на бусти: https://boosty.to/sobolevn
| Поддержать | YouTube | GitHub | Чат |
Python Enhancement Proposals (PEPs)
PEP 827 – Type Manipulation | peps.python.org
We propose adding powerful type-level introspection and construction facilities to Python’s type system. This design is inspired largely by TypeScript’s conditional and mapped types, but is adapted to the distinct semantics and constraints of Python’s t...
4🔥100👍30🤔19💩13❤10👎10🤮4🎉2👌1
django-modern-rest@0.1.0 – первый публичный релиз!
Исходники: https://github.com/wemake-services/django-modern-rest
Подробнейшая документация: https://django-modern-rest.readthedocs.io
Пример настоящего приложения: https://github.com/wemake-services/wemake-django-template
Первый анонс был уже какое-то время назад.
Так что давайте повторять, что у нас тут происходит.
Во-первых, у нас рекорд: еще нет ни одного релиза, а уже 560+ ⭐ на Гитхабе (сходите поставьте, кто еще не).
Вижу, что люди ждут, вижу интерес. Спасибо!
Фичи
– Главная фича, которая вообще подтолкнула меня к такому проекту: инфраструктура Джанги. Тут есть буквально все пакеты на все случаи жизни. Но не было нормального REST фреймворка. В комментах я регулярно наблюдал, как люди ненавидят Джангу, но почти всегда говорят про DRF. Да, он был ужасен – но теперь он на свалке истории!
– Все существующие плагины к родной Джанге должны работать
– Официальная поддержка Джанго в одном файле, да, Джанга может быть настолько простой
– Работаем с любыми моделями: pydantic, msgspec, TypedDict, dataclass, тд. Сериализация и валидация не прибиты гвоздями. А значит можно выбирать сериализатор под контроллер. Где-то msgspec + TypedDict для скорости. Где-то pydantic для более широких возможностей валидации. Можно писать свои
– Скорость. Мы довольно быстрые. Самый быстрый Python фреймворк для REST в Django. По скорости можно сравнивать с FastAPI, мы всего лишь на 30% медленнее. Но у нас и Джанга вообще-то. Скорость будет улучшаться, есть разные интересные идеи
– Типизация: типизировано всё! Но самое важное, типизацию не пихают вам в лицо. Нет огромных и сложных типов. Все просто, надежно и удобно. Поддерживаем
– Поддержка
– SSE! Без дополнительных костылей: просто работает (с валидацией сообщений и возможностью строить бизнесовые ADT поверх типов сообщений и крутейшей схемой)
– Семантика. Одна из ключевых фичей: мы очень сильно упоролись по генерации схемы. Добавил
– Swagger, Scalar, Redoc из коробки, легко настраивать
– Работаем не только с json, поддерживаем content negotiation, можно писать свои парсеры и рендереры
– JWT и DjangoSessionAuth из коробки, есть возможность отзыва токенов и сессий
– Возможность писать заготовки контроллеров и полностью переиспользовать код. Писать плагины под
– Загрузка и отдача файлов (но на питоне такое очень осторожно надо делать, лучше на Rust)
– Нет привязки к логике или DI (берите любой, например dishka). Мы просто парсим данные и возвращаем их. То есть: код не превратится в кашу из логики и фреймворка уже через 10 бизнес фичей
– Удобная обработка ошибок на многих уровнях
– Полная возможность для кастомизации. Можно даже поменять формат внутренних ошибок в рамках контроллера
– Удобные тесты:
– Скилы для LLM для написания кода по OpenAPI спеке,
– Но никакого нейрослопа внутри!
Исходники: https://github.com/wemake-services/django-modern-rest
Подробнейшая документация: https://django-modern-rest.readthedocs.io
Пример настоящего приложения: https://github.com/wemake-services/wemake-django-template
Первый анонс был уже какое-то время назад.
Так что давайте повторять, что у нас тут происходит.
Во-первых, у нас рекорд: еще нет ни одного релиза, а уже 560+ ⭐ на Гитхабе (сходите поставьте, кто еще не).
Вижу, что люди ждут, вижу интерес. Спасибо!
import uuid
import msgspec
from dmr import Body, Controller
from dmr.plugins.msgspec import MsgspecSerializer
class UserCreateModel(msgspec.Struct):
email: str
class UserModel(UserCreateModel):
uid: uuid.UUID
class UserController(
Controller[MsgspecSerializer],
Body[UserCreateModel],
):
def post(self) -> UserModel:
return UserModel(uid=uuid.uuid4(), email=self.parsed_body.email)
Фичи
– Главная фича, которая вообще подтолкнула меня к такому проекту: инфраструктура Джанги. Тут есть буквально все пакеты на все случаи жизни. Но не было нормального REST фреймворка. В комментах я регулярно наблюдал, как люди ненавидят Джангу, но почти всегда говорят про DRF. Да, он был ужасен – но теперь он на свалке истории!
– Все существующие плагины к родной Джанге должны работать
– Официальная поддержка Джанго в одном файле, да, Джанга может быть настолько простой
– Работаем с любыми моделями: pydantic, msgspec, TypedDict, dataclass, тд. Сериализация и валидация не прибиты гвоздями. А значит можно выбирать сериализатор под контроллер. Где-то msgspec + TypedDict для скорости. Где-то pydantic для более широких возможностей валидации. Можно писать свои
– Скорость. Мы довольно быстрые. Самый быстрый Python фреймворк для REST в Django. По скорости можно сравнивать с FastAPI, мы всего лишь на 30% медленнее. Но у нас и Джанга вообще-то. Скорость будет улучшаться, есть разные интересные идеи
– Типизация: типизировано всё! Но самое важное, типизацию не пихают вам в лицо. Нет огромных и сложных типов. Все просто, надежно и удобно. Поддерживаем
mypy, pyright, pyrefly в самых строгих вариантах– Поддержка
async везде. От вьюх и моделей до SSE. Никаких sync_to_async внутри– SSE! Без дополнительных костылей: просто работает (с валидацией сообщений и возможностью строить бизнесовые ADT поверх типов сообщений и крутейшей схемой)
– Семантика. Одна из ключевых фичей: мы очень сильно упоролись по генерации схемы. Добавил
auth= в контроллер? В списке ответов появился 401 статус код автоматически. Возвращаешь ответ, заголовок, куку, которой нет в спеке? Во время дебага – случится ошибка валидации. На проде валидацию нужно отключать для скорости. Так мы гарантируем точность ответов и схемы. Не нравится схема? Все легко переопределить или вообще отключить– Swagger, Scalar, Redoc из коробки, легко настраивать
– Работаем не только с json, поддерживаем content negotiation, можно писать свои парсеры и рендереры
– JWT и DjangoSessionAuth из коробки, есть возможность отзыва токенов и сессий
– Возможность писать заготовки контроллеров и полностью переиспользовать код. Писать плагины под
dmr будет просто и удобно– Загрузка и отдача файлов (но на питоне такое очень осторожно надо делать, лучше на Rust)
– Нет привязки к логике или DI (берите любой, например dishka). Мы просто парсим данные и возвращаем их. То есть: код не превратится в кашу из логики и фреймворка уже через 10 бизнес фичей
– Удобная обработка ошибок на многих уровнях
– Полная возможность для кастомизации. Можно даже поменять формат внутренних ошибок в рамках контроллера
– Удобные тесты:
polyfactory, pytest, schemathesis (проходим все правила из коробки)– Скилы для LLM для написания кода по OpenAPI спеке,
llms-full.txt, Context7 для контекста– Но никакого нейрослопа внутри!
GitHub
GitHub - wemake-services/django-modern-rest: Modern REST framework for Django with types and async support!
Modern REST framework for Django with types and async support! - wemake-services/django-modern-rest
86🔥229👏34❤21👍11🎉8💩4🤔3😢1🤮1
Начало: https://xn--r1a.website/opensource_findings/950
Что будет дальше?
– Доработка доки. Я хочу, чтобы люди заново открывали для себя Джангу (лучший фреймворк для веба на питоне, имхо). Изучали лучшие практики, думали про архитектуру. Сейчас дока в хорошем состоянии, но нет предела совершенству
– Мы еще даже не пробовали значительно ускорить проект. В рамках идей: переписывания кусков на Rust, Cython, компиляция кода mypyc
– Поддержка WebSocket без
– Поддержка других форматов стриминга кроме SSE, например
– Поддержка
– Скилы для LLM для автоматизации перехода с
– Поддержка
Благодарности
Данный проект не стал бы возможен без:
– Александра и Алексея – соавторов проекта, они затащили гигантский объем работы
– Виктора, кто сделал нам офигительную интерактивную доку!
– А так же 51 других контрибьюторов, кто внес неоценимый вклад в проект
Большое спасибо всем за помощь, обратную связи и поддержку, без вас – ничего бы не вышло.
Лучшее сообщество! 🫶
Ну а я – делаю небольшой перерыв, отдыхаю и работаю дальше!
Обсуждение: какие фичи вы бы хотели увидеть в дальнейших релизах?
P.S. Если у вас есть подкаст / канал / тд, и вы хотите поговорить со мной про веб фреймворки на питоне – пишите в личку! Сделаем интересное :)
| Поддержать | YouTube | GitHub | Чат |
Что будет дальше?
– Доработка доки. Я хочу, чтобы люди заново открывали для себя Джангу (лучший фреймворк для веба на питоне, имхо). Изучали лучшие практики, думали про архитектуру. Сейчас дока в хорошем состоянии, но нет предела совершенству
– Мы еще даже не пробовали значительно ускорить проект. В рамках идей: переписывания кусков на Rust, Cython, компиляция кода mypyc
– Поддержка WebSocket без
django-channels (фу, Артём, без негатива)– Поддержка других форматов стриминга кроме SSE, например
JsonL– Поддержка
cattrs и adaptix– Скилы для LLM для автоматизации перехода с
django-rest-framework и django-ninja– Поддержка
tyБлагодарности
Данный проект не стал бы возможен без:
– Александра и Алексея – соавторов проекта, они затащили гигантский объем работы
– Виктора, кто сделал нам офигительную интерактивную доку!
– А так же 51 других контрибьюторов, кто внес неоценимый вклад в проект
Большое спасибо всем за помощь, обратную связи и поддержку, без вас – ничего бы не вышло.
Лучшее сообщество! 🫶
Ну а я – делаю небольшой перерыв, отдыхаю и работаю дальше!
Обсуждение: какие фичи вы бы хотели увидеть в дальнейших релизах?
P.S. Если у вас есть подкаст / канал / тд, и вы хотите поговорить со мной про веб фреймворки на питоне – пишите в личку! Сделаем интересное :)
| Поддержать | YouTube | GitHub | Чат |
Telegram
Находки в опенсорсе
django-modern-rest@0.1.0 – первый публичный релиз!
Исходники: https://github.com/wemake-services/django-modern-rest
Подробнейшая документация: https://django-modern-rest.readthedocs.io
Пример настоящего приложения: https://github.com/wemake-services/wemake…
Исходники: https://github.com/wemake-services/django-modern-rest
Подробнейшая документация: https://django-modern-rest.readthedocs.io
Пример настоящего приложения: https://github.com/wemake-services/wemake…
8👍131❤48👏21🔥10🤔2💩2😢1
Находки в опенсорсе pinned «django-modern-rest@0.1.0 – первый публичный релиз! Исходники: https://github.com/wemake-services/django-modern-rest Подробнейшая документация: https://django-modern-rest.readthedocs.io Пример настоящего приложения: https://github.com/wemake-services/wemake…»
PEP 828: async yield from и состояние асинхронных генераторов в питоне
PEP: https://peps.python.org/pep-0828
Обсуждение: https://discuss.python.org/t/pep-828-supporting-yield-from-in-asynchronous-generators/106459
Код: https://github.com/python/cpython/pull/145716
В питон хотят добавить
Во-первых, оно реально иногда удобно. Во-вторых, реально консистентно визуально и синтаксически с синхронными генераторами:
С другой стороны: https://pyfound.blogspot.com/2024/06/python-language-summit-2024-limiting-yield-in-async-generators.html
> Guido van Rossum lamented that this was "yet another demonstration that async generators were a bridge too far. Could we have a simpler PEP that proposes to deprecate and eventually remove from the language asynchronous generators, just because they're a pain and tend to spawn more complexity".
> Zac had no objections to a PEP deprecating async generators¹. Zac continued, "while static analysis is helpful in some cases, there are inevitably cases that it misses which kept biting us... until we banned all async generators in our codebase".
И вроде бы на примере выше оно выглядит нормально. Но давайте чуть глубже посмотрим.
Кстати, недавно наш коллега – Сергей Мирянов – добавил секцию "Async generators best practices" в доку asyncio.
Всем советую: https://docs.python.org/3.15/library/asyncio-dev.html#asynchronous-generators-best-practices
Если вы можете найти в данном коде 4 ошибки, то можете не читать доку. Остальным обязательно.
Какие проблемы там подсвечены?
1. Явное использование
2. Порядок очистки ресурсов в асинхронных генераторах может быть не таким, как вы думаете
3. Запуск асинхронных генераторов без event loop - плохая идея
4. Итерация асинхронного генератора из двух разных тасок = ошибка
Так вот! Стоит ли углубляться туда?
Обсуждение: что вы думаете про асинхронные генераторы и их развитие? Можете ли честно сказать, что понимаете, как они работают? Можете найти баги с
| Поддержать | YouTube | GitHub | Чат |
PEP: https://peps.python.org/pep-0828
Обсуждение: https://discuss.python.org/t/pep-828-supporting-yield-from-in-asynchronous-generators/106459
Код: https://github.com/python/cpython/pull/145716
В питон хотят добавить
async yield from. И у меня есть много разных мыслей.Во-первых, оно реально иногда удобно. Во-вторых, реально консистентно визуально и синтаксически с синхронными генераторами:
async def agenerator():
yield 1
yield 2
async def main():
async yield from agenerator()
С другой стороны: https://pyfound.blogspot.com/2024/06/python-language-summit-2024-limiting-yield-in-async-generators.html
> Guido van Rossum lamented that this was "yet another demonstration that async generators were a bridge too far. Could we have a simpler PEP that proposes to deprecate and eventually remove from the language asynchronous generators, just because they're a pain and tend to spawn more complexity".
> Zac had no objections to a PEP deprecating async generators¹. Zac continued, "while static analysis is helpful in some cases, there are inevitably cases that it misses which kept biting us... until we banned all async generators in our codebase".
И вроде бы на примере выше оно выглядит нормально. Но давайте чуть глубже посмотрим.
Кстати, недавно наш коллега – Сергей Мирянов – добавил секцию "Async generators best practices" в доку asyncio.
Всем советую: https://docs.python.org/3.15/library/asyncio-dev.html#asynchronous-generators-best-practices
Если вы можете найти в данном коде 4 ошибки, то можете не читать доку. Остальным обязательно.
import asyncio
work_done = False
async def cursor():
try:
yield 1
finally:
assert work_done
async def rows():
global work_done
try:
yield 2
finally:
await asyncio.sleep(0.1) # immitate some async work
work_done = True
async def main():
async for c in cursor():
async for r in rows():
break
break
asyncio.run(main())
Какие проблемы там подсвечены?
1. Явное использование
aclosing(agen) контекста для закрытия AsyncGenerator, иначе может пропасть стадия "уборки за собой", а сам генератор может остаться живым2. Порядок очистки ресурсов в асинхронных генераторах может быть не таким, как вы думаете
3. Запуск асинхронных генераторов без event loop - плохая идея
4. Итерация асинхронного генератора из двух разных тасок = ошибка
Так вот! Стоит ли углубляться туда?
Обсуждение: что вы думаете про асинхронные генераторы и их развитие? Можете ли честно сказать, что понимаете, как они работают? Можете найти баги с
asyncio.CanceledError и очисткой состояния без запуска кода? Я - нет.| Поддержать | YouTube | GitHub | Чат |
1❤29👍16😢6🔥1🤔1
И сразу бонусом хочу напомнить, что такое обычное выражение
Источник: https://peps.python.org/pep-0380
Никогда не спрашивайте такое на собесах, будьте людьми 🌚
Что будет тут?
Страшно. Очень страшно.
P.S. Два поста в один день, когда такое было?!
RESULT = yield from EXPR в CPython.
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
Источник: https://peps.python.org/pep-0380
Никогда не спрашивайте такое на собесах, будьте людьми 🌚
Что будет тут?
async def agenerator():
yield 1
return 2
async def main():
result = async yield from agenerator()
assert result == 2
Страшно. Очень страшно.
P.S. Два поста в один день, когда такое было?!
Python Enhancement Proposals (PEPs)
PEP 380 – Syntax for Delegating to a Subgenerator | peps.python.org
A syntax is proposed for a generator to delegate part of its operations to another generator. This allows a section of code containing ‘yield’ to be factored out and placed in another generator. Additionally, the subgenerator is allowed to return with ...
2😁56🤯19❤12
tracecov: считаем покрытие АПИ через спецификацию OpenAPI
Вышла новая версия
И там мы выпустили поддержку
В чем суть? Там мы считаем не "покрытие кода", а намного более важную метрику: "покрытие тестами нашего АПИ". Ну то есть буквально:
• Какие операции были вызваны?
• С какими телами и параметрами?
• Какие ответы получены по статусам?
• Какие схемы возвращены?
• Работают ли примеры из доки?
Так как мы используем очень строгую схему - у нас такой подход хорошо работает.
Мы интегрировали поддержку
Один запуск
В
И тогда тесты будут падать при низком покрытии АПИ. Вот куда можно развиваться, если у вас - как у нас - уже 100% обычного покрытия.
Одной строкой
• Добавили поддержку
• Добавили
• Добавили
• Переработали несколько апишек, стало значительно удобнее. Спасибо первым пользователям за обратную связь!
Обсуждение: Воспользовались бы такой метрикой? И какое покрытие вы считаете оптимальным? И почему 100%?
P.S. Выпустил большую статью про
| Поддержать | YouTube | GitHub | Чат |
Вышла новая версия
0.4.0 https://github.com/wemake-services/django-modern-restИ там мы выпустили поддержку
tracecov. Инструмент новый, такого в других фреймворках я не видел.В чем суть? Там мы считаем не "покрытие кода", а намного более важную метрику: "покрытие тестами нашего АПИ". Ну то есть буквально:
• Какие операции были вызваны?
• С какими телами и параметрами?
• Какие ответы получены по статусам?
• Какие схемы возвращены?
• Работают ли примеры из доки?
Так как мы используем очень строгую схему - у нас такой подход хорошо работает.
Мы интегрировали поддержку
tracecov в наш dmr_client, который используется для всех интеграционных тестов. И schemathesis, который мы используем для property-based тестирования OpenAPI спецификации - тоже поддерживает такое.Один запуск
schemathesis позволяет добиться примерно 85+% покрытия всего АПИ. Вау! То есть: тесты можно почти не писать с таким походом.В
pyproject.toml можно добавить:
# 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",
И тогда тесты будут падать при низком покрытии АПИ. Вот куда можно развиваться, если у вас - как у нас - уже 100% обычного покрытия.
Одной строкой
• Добавили поддержку
attrs для моделей• Добавили
msgpack как протокол для АПИ, он значительно быстрее json• Добавили
JsonLines для стриминга событий• Переработали несколько апишек, стало значительно удобнее. Спасибо первым пользователям за обратную связь!
Обсуждение: Воспользовались бы такой метрикой? И какое покрытие вы считаете оптимальным? И почему 100%?
P.S. Выпустил большую статью про
django-modern-rest на Хабру: https://habr.com/ru/articles/1017036 Если есть плюсики - буду очень благодарен за помощь в продвижении!| Поддержать | YouTube | GitHub | Чат |
21🔥102🎉16❤12👍7💩2
Нас всех заменят!
Сегодня я открыл для себя вайбкодинг. Да, модели действительно пишут код лучше людей.
За несколько часов я смог сделать больше, чем за месяц до.
Конечно, потребовались некоторые изменения рабочего процесса.
Из самого важного:
• Описывать контекст по частям
• Делать строгие
• Использовать последние SOTA модели
• Пользоваться скилами готовыми под нужные технологии
Еще я заметил, что некоторые языки подходят лучше, чем другие.
Пока остановился на Go. Язык очень приятно выглядит. Он простой, но выразительный.
Из-за его продвинутой статической типизации и универсальности писать на нем большие проекты будет очень удобно.
Как быстро меняется мир!
Обсуждение: В комментах посоветуйте своё любимое аниме? Смотреть нечего!
Хорошего праздника! #ironid:
UPD: Первое апреля закончилось :(
| Поддержать | YouTube | GitHub | Чат |
Сегодня я открыл для себя вайбкодинг. Да, модели действительно пишут код лучше людей.
За несколько часов я смог сделать больше, чем за месяц до.
Конечно, потребовались некоторые изменения рабочего процесса.
Из самого важного:
• Описывать контекст по частям
• Делать строгие
AGENT.md• Использовать последние SOTA модели
• Пользоваться скилами готовыми под нужные технологии
Еще я заметил, что некоторые языки подходят лучше, чем другие.
Пока остановился на Go. Язык очень приятно выглядит. Он простой, но выразительный.
Из-за его продвинутой статической типизации и универсальности писать на нем большие проекты будет очень удобно.
Как быстро меняется мир!
Обсуждение: В комментах посоветуйте своё любимое аниме? Смотреть нечего!
Хорошего праздника! #ironid:
c435ff72UPD: Первое апреля закончилось :(
| Поддержать | YouTube | GitHub | Чат |
boosty.to
sobolevn - OpenSource & Education & Настольные игры
Привет! Я делаю множество OpenSource проектов: https://github.com/sobolevn В их числе: CPython, mypy, typeshed, dry-python, TypedDjango, проекты внутри wemake-services (wemake-python-styleguide и другие), hypothesis, git-secret и еще много всякого!…
3😁311🤡38🔥13❤11🎉11💩6🤯5😢3👎1🤔1🤩1
cibuildwheel: делаем колеса в промышленных масштабах
Ссылка: https://github.com/pypa/cibuildwheel
Привет! Вы наверняка когда-нибудь задумывались, откуда берутся все те замечательные wheel пакеты под разные системы и архитектуры для наших любимых зависимостей с бинарными частями. Например: mypy, black, тд.
Вот и я - нет!
Но, когда мне для релиза
Как оно работает?
Пакет - достаточно простой, обычная клиха (украл слово у @diementros). При запуске - указываем какой
Соберет вам текущий пакет для
Как будет проходить сборка? Полный лог: https://github.com/wemake-services/django-modern-rest/actions/runs/24023507014/job/70057082696#step:4:195
1. Сначала устанавливается нужный питон из готовых образов
2. Подготавливаем окружение
3. Запускаем
Например, у нас она выглядит так (мы используем
Вторая часть задачи: запустить сам
Тут мы указываем: что билдим, что по-умолчанию билд с mypyc выключен, какие зависимости для билда нужны и что нужно поставить рантайм зависимости для билда. Билдить с mypyc будем только если есть специальный флаг:
Только когда он есть (или мы билдим с
4. Запускаем тесты собранного
5. Замеряем, что наши скомпилированные части реально стали работать быстрее
Готово!
Запускаем в CI
Последняя часть: нужно как-то запустить CI с 50+ разных вариантов конфигураций.
Далее дело техники, собираем матрицу всех задач для нужной CI и запускаем такую матрицу:
Самая хитрая часть тут в
Получается удобно и довольно просто.
Последним шагом мы просто загружаем данные пакеты при
И вот так - к вам приехал новый релиз
Анонс митапа в Нижнем
Кстати, у нас скоро будет PythoNN митап в Нижнем Новгороде со всеми вашими любимыми спикерами: @diementros @pymineral, а еще Роман Фролов и Михаил Васильев.
17 апреля, начало в 18:30.
Регистрация: https://pytho-nn.timepad.ru/event/3880099
Приезжайте, приходите. Будет много пива, настолок, разговоров про питон.
Обсуждение: чем вы билдите колеса на работе? Нужно ли вообще такое где-то, кроме опенсорса?
| Поддержать | YouTube | GitHub | Чат |
Ссылка: https://github.com/pypa/cibuildwheel
Привет! Вы наверняка когда-нибудь задумывались, откуда берутся все те замечательные wheel пакеты под разные системы и архитектуры для наших любимых зависимостей с бинарными частями. Например: mypy, black, тд.
Вот и я - нет!
Но, когда мне для релиза
django-modern-rest@0.5.0 потребовалось компилировать части фреймворка с mypyc для получения перформанса на ровном месте - мне пришлось разобраться. Так давайте и вам расскажу.Как оно работает?
Пакет - достаточно простой, обычная клиха (украл слово у @diementros). При запуске - указываем какой
wheel нужно собрать. Например:
cibuildwheel --only cp313-macosx_arm64 --config-file pyproject.toml
Соберет вам текущий пакет для
3.13 и macos с arm64 архитектурой. А вот конфигурация:
[tool.cibuildwheel]
build = "cp3{11,12,13,14}-*"
build-frontend = "uv"
test-command = 'your_test_command'
Как будет проходить сборка? Полный лог: https://github.com/wemake-services/django-modern-rest/actions/runs/24023507014/job/70057082696#step:4:195
1. Сначала устанавливается нужный питон из готовых образов
2. Подготавливаем окружение
3. Запускаем
build систему. Она берется из вашего pyproject.toml / setup.pyНапример, у нас она выглядит так (мы используем
uv, у которого нет родной билд системы для бинарных зависимостей, потому используем `hatch`):
[build-system]
requires = ["hatchling", "hatch", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
Вторая часть задачи: запустить сам
mypyc на нужных файлах. К hatch есть плагинчик hatch-mypy. Его надо тоже настроить:
[tool.hatch.build.targets.wheel.hooks.mypyc]
enable-by-default = false
dependencies = ["hatch-mypyc", "mypy==1.19.1"]
include = ["dmr/_compiled"]
require-runtime-dependencies = true
Тут мы указываем: что билдим, что по-умолчанию билд с mypyc выключен, какие зависимости для билда нужны и что нужно поставить рантайм зависимости для билда. Билдить с mypyc будем только если есть специальный флаг:
[tool.cibuildwheel.environment]
HATCH_BUILD_HOOKS_ENABLE = "1"
Только когда он есть (или мы билдим с
cibuildwheel), то сборка пакета запустится. Такое нужно нам, чтобы иметь возможность делать нативные python-only сборки без .so частей.4. Запускаем тесты собранного
wheel пакета с test-command, проверяем, что собранный пакет работает5. Замеряем, что наши скомпилированные части реально стали работать быстрее
Готово!
Запускаем в CI
Последняя часть: нужно как-то запустить CI с 50+ разных вариантов конфигураций.
cibuildwheel тут снова поможет. Он умеет выплевывать такие конфигурации для CI командой: CIBW_BUILD="cp313-*" cibuildwheel --print-build-identifiers --platform macos.Далее дело техники, собираем матрицу всех задач для нужной CI и запускаем такую матрицу:
mypyc:
name: mypyc wheels ${{ matrix.only }}
needs: configure
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.configure.outputs.include) }}
Самая хитрая часть тут в
include: там мы как раз динамически подставляем конфигурации от cibuildwheel.Получается удобно и довольно просто.
Последним шагом мы просто загружаем данные пакеты при
release, используя PyPI Trusted Publisher.И вот так - к вам приехал новый релиз
django-modern-rest с опциональными бинарными частями для СКОРОСТИ: https://github.com/wemake-services/django-modern-rest/releases/tag/0.5.0Анонс митапа в Нижнем
Кстати, у нас скоро будет PythoNN митап в Нижнем Новгороде со всеми вашими любимыми спикерами: @diementros @pymineral, а еще Роман Фролов и Михаил Васильев.
17 апреля, начало в 18:30.
Регистрация: https://pytho-nn.timepad.ru/event/3880099
Приезжайте, приходите. Будет много пива, настолок, разговоров про питон.
Обсуждение: чем вы билдите колеса на работе? Нужно ли вообще такое где-то, кроме опенсорса?
| Поддержать | YouTube | GitHub | Чат |
GitHub
GitHub - pypa/cibuildwheel: 🎡 Build Python wheels for all the platforms with minimal configuration.
🎡 Build Python wheels for all the platforms with minimal configuration. - pypa/cibuildwheel
1🔥47👍12❤3😱1