Находки в опенсорсе
11.3K subscribers
15 photos
1 video
3 files
833 links
Привет!

Меня зовут Никита Соболев. Я занимаюсь опенсорс разработкой полный рабочий день.

Тут я рассказываю про #python, #c, опенсорс и тд.
Поддержать: https://boosty.to/sobolevn
РКН: https://vk.cc/cOzn36

Связь: @sobolev_nikita
Download Telegram
Зачем нужен новый 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-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🔥7763💩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


"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 | Чат |
3🔥7016👍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


#[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 | Чат |
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 и некоторые их варианты для дебага.

Они разделены на три "домена" для аллокаторов, то с чем они работают, какие задачи решают:
- 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 | Чат |
1🔥75👍3611👏2👌1🕊1
git-lfs: храним большие файлы в репозитории правильно

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 | Чат |
52🔥58👍2210👏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 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🔥7511🎉4
PEP-800: typing.disjoint_base

ПЕП: 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 | Чат |
4🤔61👍5830🔥17🤯6🤡3💩2😱1🕊1
Обстановка в опенсорсе прямо сейчас: https://github.com/wemake-services/wemake-python-styleguide/issues/3596

А пока - все вместе ждем релиза django_modern_rest, уже скоро.
3😁226🤡3713🔥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
Исходники (да, они с 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 | Чат |
3👍169🔥4511🎉5😢2🥰1😁1👌1
PEP-747: TypeForm, или "аннотируем аннотации"

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 | Чат |
268🔥26👍22💩4👌1
PEP-827: Самое интересное, что случалось с типами в питоне!

Текст: 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 | Чат |
4🔥100👍30🤔19💩1310👎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+ на Гитхабе (сходите поставьте, кто еще не).
Вижу, что люди ждут, вижу интерес. Спасибо!


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 для контекста
– Но никакого нейрослопа внутри!
86🔥229👏3421👍11🎉8💩4🤔3😢1🤮1
Начало: https://xn--r1a.website/opensource_findings/950

Что будет дальше?

– Доработка доки. Я хочу, чтобы люди заново открывали для себя Джангу (лучший фреймворк для веба на питоне, имхо). Изучали лучшие практики, думали про архитектуру. Сейчас дока в хорошем состоянии, но нет предела совершенству
– Мы еще даже не пробовали значительно ускорить проект. В рамках идей: переписывания кусков на Rust, Cython, компиляция кода mypyc
– Поддержка WebSocket без django-channels (фу, Артём, без негатива)
– Поддержка других форматов стриминга кроме SSE, например JsonL
– Поддержка cattrs и adaptix
– Скилы для LLM для автоматизации перехода с django-rest-framework и django-ninja
– Поддержка ty

Благодарности

Данный проект не стал бы возможен без:
Александра и Алексея – соавторов проекта, они затащили гигантский объем работы
Виктора, кто сделал нам офигительную интерактивную доку!
– А так же 51 других контрибьюторов, кто внес неоценимый вклад в проект

Большое спасибо всем за помощь, обратную связи и поддержку, без вас – ничего бы не вышло.
Лучшее сообщество! 🫶

Ну а я – делаю небольшой перерыв, отдыхаю и работаю дальше!

Обсуждение: какие фичи вы бы хотели увидеть в дальнейших релизах?

P.S. Если у вас есть подкаст / канал / тд, и вы хотите поговорить со мной про веб фреймворки на питоне – пишите в личку! Сделаем интересное :)

| Поддержать | YouTube | GitHub | Чат |
8👍13148👏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

В питон хотят добавить 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 | Чат |
129👍16😢6🔥1🤔1
И сразу бонусом хочу напомнить, что такое обычное выражение 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. Два поста в один день, когда такое было?!
2😁56🤯1912
tracecov: считаем покрытие АПИ через спецификацию OpenAPI

Вышла новая версия 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🎉1612👍7💩2
Нас всех заменят!

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

Конечно, потребовались некоторые изменения рабочего процесса.
Из самого важного:
• Описывать контекст по частям
• Делать строгие AGENT.md
• Использовать последние SOTA модели
• Пользоваться скилами готовыми под нужные технологии

Еще я заметил, что некоторые языки подходят лучше, чем другие.
Пока остановился на Go. Язык очень приятно выглядит. Он простой, но выразительный.
Из-за его продвинутой статической типизации и универсальности писать на нем большие проекты будет очень удобно.

Как быстро меняется мир!

Обсуждение: В комментах посоветуйте своё любимое аниме? Смотреть нечего!

Хорошего праздника! #ironid: c435ff72

UPD: Первое апреля закончилось :(

| Поддержать | YouTube | GitHub | Чат |
3😁311🤡38🔥1311🎉11💩6🤯5😢3👎1🤔1🤩1
cibuildwheel: делаем колеса в промышленных масштабах

Ссылка: 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 | Чат |
1🔥47👍123😱1