Коробка с питоном
538 subscribers
45 photos
119 links
Заметки от Python-разработчика: сниппеты, обзоры пакетов, новости и другая полезная информация.

Автор: @kiriharu
Download Telegram
Если вы любите делать игры на Godot или хотите начать их делать, при этом разбираться с GDScript вам лень - вы можете попробовать использовать godot-python. Godot - это прекраснейший движок, чтобы начать разрабатывать игры. Как по мне выглядит получше Unity, особенно для всяких небольших мобильных игр (особенно на фоне последних новостей).

Ребята пытаются добавить поддержку скриптинга на Python в Godot, и судя по тому как развивается проект - получается это у них довольно неплохо.

Из минусов я бы выделил: странный механизм экспорта проекта, вместе с игрой вы экспортите интерпретатор питона (хотя для кого-то это не минус), отсутствие pip из под коробки - его ставить нужно отдельно.

#библиотека #gamedev
Я всё чаще и чаще замечаю Rust рядом с Python...

Совершенно недавно я нашел вот такой HTTP сервер на Rust для приложений на Python - Granian.

Разработчики ставят перед собой следующие цели:
+ Поддержка HTTP версий 1, 2 и 3
+ Один пакет для всех платформ
+ Сервер должен заменить Gunicorn/Uvicorn
+ Обеспечение большей производительности по сравнению с конкурентами.

Сейчас оно умеет в:
+ ASGI/3 и RSGI
+ HTTP/1 и HTTP/2
+ Вебсокеты над HTTP/1 и HTTP/2
+ Поддержка SSL

К сожалению проект пока что в состоянии разработки, поддерживает Питон 3.7 и выше на Unix-платформах и 3.8 и выше на Windows.
Очень надеюсь что с этого что-то вырастет.

#библиотека
Коробка с питоном
Я всё чаще и чаще замечаю Rust рядом с Python... Совершенно недавно я нашел вот такой HTTP сервер на Rust для приложений на Python - Granian. Разработчики ставят перед собой следующие цели: + Поддержка HTTP версий 1, 2 и 3 + Один пакет для всех платформ…
К слову, эти же чуваки сделали довольно известный в узких кругах фреимворк emmett.

Это фреимворк с кучей полезностей и различными новыми подходами. Вот самое интересное:

1) ORM на pyDAl, которая умеет даже в Монгу и FireBird писать! Ну и миграции, каллбеки (похожи на сигналы в Django) и ещё огромная куча всего, почитайте.
2) Валидаторы, правда не такие красивые как c Pydantic.
3) Система авторизации с гибкой системой групп.
4) Даже различные бекенды для кеша есть!
5) Очень интересная система пайплайнов, чем-то похожая на middleware, но с возможностью делать различные действия в зависимости от состояния пайпа.
6) Не менее интересная система сервисов, которая позволяет отдавать ответ не только в html, а в каком-то json или xml.
7) Система расширений.
8) Ну и CLI.
9) А для тех кому нужен REST есть расширение.

Уверен, что почти каждый читающий не знал о существовании этого фреймворка, а жаль. К счастью или сожалению сейчас господствуют микрофреимворки испытывающие unix-way - делай что-то одно и делай это хорошо.

#библиотека
Уже какую неделю подряд на Reddit (да и в англоязычных Python комьюнити) хайпит Flet.

Flet - это фреимворк который позволяет строить интерактивные десктопные/мобильные/веб приложения на различных ЯП (пока что увидел только поддержку Python), в итоге получаем SPA/PWA. Построено это всё на Flutter. Пример кода на скриншоте.

Подход с декларативным построением интерфейса вообще не новый - взгляните на Swift UI или же Jetpack Compose. Вопрос лишь в том, кто это использует?

Мне кажется, что:
1. Скорее всего подойдет для тех, кому нужно быстро накостылять MVP или взять замену tkinter.
2. Кейса, где это потащат в прод я не могу придумать по одной простой причине - множество гвоздей забивать микроскопом неудобно. Лучше взять молоток в виде React и спокойно с ним работать.

Во всяком случае - выглядит прикольно. Есть документация с примерами.

#библиотека
Продолжаем смотреть на питоновские библиотеки! На этот раз снова не обойдется без влияния Rust - сегодня мы будем рассматривать библиотеки которые реализуют типы Result и Option.

В стандартной библиотеке Rust есть такой тип как Option - он позволяет, в случае если что-то пошло не так вернуть None, иначе вернуть само значение. Ещё есть Result, который позволяет вернуть либо значение, либо ошибку, которую далее уже должна обработать вызывающая функция.
Всё это нужно по причине того, что в Rust нет конструкции try... except и господствует ФП, которое позволяет строить мощные функциональные конвейеры для обработки получаемых внутри программы данных.

Зачем это нужно в Python? А оно там не нужно Есть несколько причин:
1) Вы пишите на Rust
3) Такой подход вам ближе
2) Вы пишите в парадигме функционального программирования.

В остальном, как по мне, это просто не нужно. Множить подходы (особенно к обработке ошибок) - самое неприятное что можно сделать в языке.

Но мы продолжаем. Для реализации этих типов есть несколько питоновских библиотек. Вот они: safetywrap и option. Safetywrap мне показался более полным, поэтому будем рассматривать его.

Задача - написать функцию, которая будет проверять значение на отрицательность. Если оно отрицательное - выводим ошибку. С safetywrap это будет выглядеть следующим образом:

def check_value_not_negative(val: int) -> Result[int, str]:
if val >= 0:

return Ok(val)
return Err(f"{val} is negative!")

Как использовать эту функцию? Можно просто её вызвать и в зависимости от результата мы получим либо тип Ok, либо тип Err.

print(check_value_not_negative(1)) # Ok(1)
print(check_value_not_negative(-1)) # Err('-1 is negative!')

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

print(check_value_not_negative(-1).or_(None)) # None

Или проверить, равно ли значение единице, и если нет - вывести сообщение об этом:

print(check_value_not_negative(2).and_then(
lambda result: Result.ok_if(lambda x: x == 1, result).map_err(
lambda _: "Не равно единице"
)
)) # Err('Не равно единице')

Как видно, эти типы позволяют писать код, который будет эффективно обрабатывать ошибки, не прибегая к куче if и try...except... Мне нравится такой подход, а вам?

#библиотека
Когда-то давно, когда я только изучал Django, мне хотелось избавиться от бойлерплейта в темплейтах и вынести некоторые части шаблонов (например кнопки) в какие-то отдельные сущности которые можно переиспользовать - например в виде компонентов.

К счастью, в Django есть решение для этого и оно называется template tags. Но у него есть несколько проблем:
1) Необходимо пробрасывать руками js и css зависимости для конкретного тега там, где он используется.
2) Теги плохо кастомизируются, например нет возможности изменить поведение тега, обязательно нужно его переписывать.

Решением этих вопросов занимается пакет с названием django-components. Он предоставляет возможность делать простые, но в то же время мощные переиспользуемые компоненты. А как он справляется с проблемами выше?
1) При объявлении компонента будут грузиться только те js и css, которые указаны в классе компонента. Класс компонента выглядит как-то так:

from django_components import component

@component.register("calendar")
class Calendar(component.Component):
template_name = "calendar/calendar.html"

def get_context_data(self, date):
return {
"date": date,
}

class Media:
css = "calendar/calendar.css"
js = "calendar/calendar.js"

Сам js/css рендерится только там, где указаны теги component_js_dependencies и component_css_dependencies.

2) Для изменения поведения компонента можно использовать слоты - это что-то вроде django-блоков внутри компонента. Например, мы можем сделать блок body внутри компонента и изменять его вид тогда, когда нам нужно:

<div class="calendar-component">
<div class="header">
{% slot "header" %}Заголовок календаря{% endslot %}
</div>
<div class="body">
{% slot "body" %}Сегодня <span>{{ date }}</span>{% endslot %}
</div>
</div>

А теперь импортируем компонент и меняем его body:
{% component_block "calendar" date="2020-06-06" %}
{% slot "body" %}А сегодня точно <span>{{ date }}</span>?{% endslot %}
{% endcomponent_block %}

Github | PyPi

#django #библиотека
Если вы хоть раз задумывались о том, можно ли фичи FastAPI (автогенерация OpenAPI, интеграция с Pydantic, поддержка асинхронности и т.д.) добавить в Django, то я пришел вас обрадовать - есть такой проект под названием django-ninja.

Из приятных фич, которые можно встретить здесь:
1) Версионирование и возможность создания нескольких API инстансов со своей авторизацией и т.д.
2) Класс схемы интегрирован с модельками Django, поэтому можно писать что-то вроде такого:

@api.get("/tasks", response=List[TaskSchema])
def tasks(request):
return Task.objects.all()

3) Можно делать схемы из моделей, прям как в DRF.
4) Пагинация! При чем в документации показано, как можно сделать собственный кастомный класс для неё.
5) Поддержка кастомных рендереров ответов, что позволяет перевести сериализацию на ORJSON или отдавать ответы в XML, например.
6) Работа с аутентификацией из под коробки.
7) Ну и асинхронность, тем более разработчики Django работают над ней.
А в остальном он очень похож на FastAPI, особенно когда речь идет о работе с аргументами, схемами и OpenAPI.

Как по мне, выглядит как достойная замена DRF, при чем довольно простая. И об этом я говорю не просто так - в последнее время я делал проекты на FastAPI и возвращаться на Django и тем более DRF было очень непривычно.

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

#django #библиотека
Если обычного itertools вам мало, то можно использовать more-itertools.

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

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

data = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh"]
[list(l) for l in divide(3, data)]
# [['first', 'second', 'third'], ['fourth', 'fifth'], ['sixth', 'seventh']]

Вот еще задача. Надо разделить список с элементами по определенному условию. В этом нам поможет bucket:

class Cat:
pass
class Dog:
pass
shapes = [Cat(), Dog(), Cat(), Dog(), Cat(), Cat()]
result = more_itertools.bucket(shapes, key=lambda x: type(x))
len(list(result[Cat])) # 4
len(list(result[Dog])) # 2

По итогу кошки и собаки будут разделены по своему типу на 2 генератора.

Решим самую частую проблему - перевести список с несколькими уровнями вложенностями в "плоский" список:

iterable = [(1, 2), ([3, 4], [[5], [6]])]
list(more_itertools.collapse(iterable)) #[1, 2, 3, 4, 5, 6]

А если в плоский список нам нужно вытащить только элементы с первым уровнем вложенности?

list(more_itertools.collapse(iterable, levels=1)) # [1, 2, [3, 4], [[5], [6]]]

А вот так мы можем посмотреть, все ли элементы в коллекции уникальные:

more_itertools.all_unique([1, 2, 3, 4]) # True
more_itertools.all_unique([1, 2, 1, 4]) # False

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

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

#more_itertools #itertools #библиотека #рецепт
Как раз сегодня искал фреимворк для организации работы консьюмера RabbitMQ и на глаза попался Propan - декларативный фреимворк для работы с очередями сообщений.

Для чего это нужно? На базе очередей можно построить асинхронную коммуникацию сервисов, а это привет микросервисной архитектуре!

Для сравнения, вот столько кода нам нужно написать, чтобы сделать консьюмер при помощи aio_pika:

import asyncio
import aio_pika
async def main():
connection = await aio_pika.connect_robust(
"amqp://guest:guest@127.0.0.1/"
)
queue_name = "test_queue"
async with connection:
channel = await connection.channel()
queue = await channel.declare_queue(queue_name)
async with queue.iterator() as queue_iter:
async for message in queue_iter:
async with message.process():
print(message.body)
asyncio.run(main())

А вот столько с Propan:

from propan import PropanApp, RabbitBroker
broker = RabbitBroker("amqp://guest:guest@localhost:5672/")
app = PropanApp(broker)
@broker.handle("test_queue")
async def base_handler(body):
print(body)

Чистый кайф, не правда ли? Выглядит просто и понятно.

Что ещё умеет?
1) Кастить типы сообщений в модельки при помощи Pydantic.
2) Умеет работать с зависимостями (привет DI)
3) Имеет CLI утилитку, которая поможет сгенерировать проект, запустить несколько процессов воркеров, запустить хот-релоад для разработки.
4) А ещё есть огромное количество примеров, как им пользоваться.
5) Бонус - похоже у автора в планах прикрутить AsyncAPI (это как OpenAPI, только для очередей).

На данный момент стабильно работает с RabbitMQ, Redis и Nats. Kafka и SQS в бете, а NatsJs, MQTT, Redis Streams и Pulsar в планах.
Ну и накиньте звёзд автору, выглядит как то, что в будущем выстрелит :)

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

#библиотека #propan
Я знаю на опыте, как иногда не удобно работать с датами и временем в Питоне.

В стандартной библиотеке есть как и большое количество типов данных (date, time, tzinfo, timedelta, relativedelta, ...) так и куча модулей (datetime, time, calendar, dateutil, ...) которые помогают работать с ними. Но содержать в голове, да еще и уметь работать с ними иногда проблематично.

Одна из библиотек, которая облегчает работу с сабжем - arrow. Давайте покажу на практике, насколько с ней удобно работать:

1) Получение времени
Начнём с легкого - получим время по MSK и UTC:
>>> arrow.now() # получаем текущее время по MSK
2023-06-02T18:46:55.188882+03:00
>>> arrow.utcnow() # а теперь текущее, но в UTC
2023-06-02T15:46:55.188882+00:00

А если нам надо текущее время с другой таимзоной?
>>> arrow.get().to("US/Pacific")
2023-06-02T08:49:39.794355-07:00

Это было просто. А что если нам нужно спарсить время из шаблона?
>>> arrow.get('2023/06-02|19:00:{00}', 'YYYY/MM-DD|HH:mm:{ss}') #обратите внимание на шаблон!
2023-06-02T19:00:00+00:00

Ещё arrow позволяет искать даты в строке по шаблону:
>>>arrow.get('Он родился 16 Мая 2020 года', 'DD MMMM YYYY', locale="ru")
2020-05-16T00:00:00+00:00

2) Работа со временем
Например, вот так можно заменить час в исходной дате:
>>>arrow.now().replace(hour=12)
2023-06-02T12:00:29.180755+03:00

Или добавить к текущей дате +5 недель:
>>>arrow.now().shift(weeks=+5)
2023-07-07T19:02:27.174819+03:00

Ну и в конце, давайте отформатируем текущее время в строку по шаблону:
>>>arrow.now().format('YYYY HH:mm:ss MM/DD')
2023 19:05:04 06/02

3) Очеловечивание времени

Возьмем задачу. Нам нужно сказать, через сколько мы прибудем домой. Делается это так:
>>> start_time = arrow.now()
>>> arrive_time = start_time.shift(hours=+3, minutes=10, seconds=12)
>>> humanized = arrive_time.humanize(locale="ru", granularity=["hour", "minute", "second"])
>>> print(f"Вы прибудете домой {humanized}")
Вы прибудете домой через 3 часа 10 минут 12 секунд

А если мы хотим из человеческого формата перевести в машинночитаемый?
>>> start_time.dehumanize(humanized, locale="ru")
2023-06-02T22:28:08.141525+03:00

4) Работа с диапазонами
Как раз недавно решал следующую задачку: дается временной промежуток, для него нужно сделать почасовые интервалы. С arrow всё это делается вот так:

start = datetime(2023, 1, 1, 00, 00)
end = datetime(2023, 1, 1, 23, 00)
for r in arrow.Arrow.span_range('hour', start, end):
print(r)

(<Arrow [2023-01-01T00:00:00+00:00]>, <Arrow [2023-01-01T00:59:59.999999+00:00]>)
....
(<Arrow [2023-01-01T23:00:00+00:00]>, <Arrow [2023-01-01T23:59:59.999999+00:00]>)

В любом случае, рекомендую присмотреться, возможно для вас эта библиотека станет одним еще довольно удобным инструментом!

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

#библиотека
Yoyo

В текущих реалиях практически любой сервис использующий реляционную БД не может обойтись без миграций. Так уж сложилось, что в нашем питонячьем мире, в проектах почти всегда можно увидеть ORM (например SQLAlchemy) и пакет, который позволяет управлять миграциями, работающий с конкретной ORM (в случае с Алхимией это alembic).

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

Yoyo - это простой мигратор схем для нашей БД. Миграции здесь записываются в виде скриптов на SQL или на Python.

Сгенерировать папку для миграций и конфиг можно вот так:
yoyo init --database sqlite:///mydb.sqlite3 migrations

А теперь создадим миграцию:
yoyo new

Опишем SQL для применения миграции (apply) и для её отката (rollback):
from yoyo import step

__depends__ = {}

steps = [
step(
"CREATE TABLE TEST (a int);",
"DROP TABLE TEST"
)
]

Теперь можно применить миграцию командой yoyo apply и после откатить её командой yoyo rollback.

Внутри базы yoyo создает таблицу с логом всех операций (_yoyo_log), таблицу с применёнными миграциями (_yoyo_migration), а так же таблицу yoyo_lock, которая позволяет заблокировать миграции, которые будут идти с нескольких инстансов приложений.

Вот такая вот простая и удобная библиотека :)

Сайт | Sourcehut | PyPI

#библиотека
Занимаюсь в рамках петпроекта обработкой текста, появилась потребность проверять на каком языке написан текст.

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

По итогу я пошел искать другую библиотеку и нашел lingua, которая успешно справляется с проблемами langdetect.
Важное отличие этой библиотеки от всех остальных в том, что она использует не только статистическую модель для определения языка, а ещё и механизмы, основанные на правилах - сначала определяется алфавит текста, ищутся символы которые уникальны для языка и после этого выбираются языки, на которых возможно написан текст.
Но есть возможность улучшить этот процесс. Можно самому ограничить языки, на которых возможно будет написан текст, а это как раз то, что мне и нужно:

from lingua import Language, LanguageDetectorBuilder
languages = [Language.ENGLISH, Language.RUSSIAN, Language.GERMAN, Language.UKRAINIAN]
detector = LanguageDetectorBuilder.from_languages(*languages).build()
print(detector.detect_language_of("Hello from box with python!")) # Language.ENGLISH
print(detector.detect_language_of("Привет от коробки с питоном!")) # Language.RUSSIAN

Из-за ограничения языков вероятность совершить ошибку на небольших предложениях сокращается многократно. В добавок ко всему, ребята используют не только триграммы, которые очень часто используют для таких задач, а n-граммы от 1 до 5, из-за чего вероятность предсказания повышается.
Причина такого решения проста - чем короче входной текст, тем меньше n-грамм доступно, а если мы будем проверять триграммами короткие заголовки - будут случаться ошибки.

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

#nlp #библиотека
Немного девопсовский пост, так как на предыдущем месте работы я много админил линукса.

Автоматизация - один из столпов работы любого админа, так как подготавливать сервера руками - занятие тяжелое и трудозатратное.
Для этого я всегда выбирал Ansible - это довольно простой инструмент, который позволяет настраивать сервера из специальных yaml конфигов, которые называются плейбуками.
Но какой бы вы инструмент не выбрали, рано или поздно конфигурация будет усложняться, а плейбуков/скриптов станет много. В какой-то момент всё это может сломаться, поэтому любую автоматизацию надо тестировать.

Когда я админил, команда не хотела полностью завязываться на Ansible (соответственно Molecule отпадает), решено было искать инструмент, который позволял бы тестировать состояние сервера после работы любой автоматизации.

И я нашел такой инструмент, имя ему - testinfra. Это плагин для pytest, который позволяет лаконично описывать тесты проверяющие состояние сервера.
Например, мы написали автоматизацию, которая устанавливает nginx. Проверим, что он установлен:

def test_nginx_is_installed(host):
nginx = host.package("nginx")
assert nginx.is_installed

А теперь проверим, что сервис с ним запущен:

def test_nginx_running_and_enabled(host):
nginx = host.service("nginx")
assert nginx.is_running
assert nginx.is_enabled

Самое важное - для него существует много connection backend'ов, которые позволяют подключаться к серверам для теста как по SSH (через paramiko), так и используя инвентарь Ansible. Так же есть бекенды для docker/podman/salt/kubectl/openshift/winrm/lxc - обо всём этом можно почитать здесь.

Напоследок, вот вам еще кейс использования: одно время при помощи testinfra я тестировал правильность сборки контейнеров - все ли есть права, запустился ли сервис, в порядке ли конфиги и так далее.

#devops #библиотека
Если вам приходилось работать с bash, например парсить логи, вы наверняка видели или использовали команды следующего вида:

cat app.log | grep 'exception' | awk '{ print $2 }'

Здесь мы последовательно считываем содержимое файла app.log, фильтруем по подстроке exception и выводим вторую колонку.
Символ |, которая разделяет команды называется "пайпом" - он перенаправляет вывод из одной команды в другую, а вся эта конструкция в целом называется конвейером, и она очень часто используется в функциональном программировании.

В Elixir есть оператор конвейера |> который позволяет передавать результат выполнения левого выражения в правое. Это позволяет делать вот такие штуки:

1..10 
|> Enum.map(fn x -> x * 2 end)
|> Enum.filter(fn x -> x > 10 end)
[12, 14, 16, 18, 20]

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

Если вам интересно. как можно сделать такое в Python, то для вас есть библиотека Pipe. Реализация очень простая - библиотека добавляет класс Pipe который переопределяет оператор |. Далее мы оборачиваем наши функции в этот класс и можем строить конвейеры!

Например, вот так мы можем получить сумму товаров, цена которых больше тысячи:

from typing import NamedTuple
from pipe import select, where

class Product(NamedTuple):
name: str
price: int

products = [Product("choco", 123), Product("auto", 10000), Product("photo", 1200)]
sum(
products
| select(lambda product: product.price)
| where(lambda x: x > 1000)
) # 11200

#библиотека
Коробка с питоном
Как раз сегодня искал фреимворк для организации работы консьюмера RabbitMQ и на глаза попался Propan - декларативный фреимворк для работы с очередями сообщений. Для чего это нужно? На базе очередей можно построить асинхронную коммуникацию сервисов, а это…
Помните, я рассказывал про такой фреимворк как Propan? В общем, программа Propan удаляется на смену Propan пришел FastStream, и весь новый функционал будет попадать в него.
Первой моей реакцией было "што" и "зачем", ведь на первый взгляд может показаться, что поменялись только орга и название. Давайте разбираться.

Во-первых, за фреимворком стоит не только сам Lancetnik, а целая команда из AirtAI, что является очень сильным усилением со стороны как разработки, так и поддержки.

Второе, FastKafka переходит в такой же статус как и Propan. Всё по той же причине - новый функционал будут добавлять в FastStream.
В свою очередь FastStream - это фреимворк, базированный на идеях FastKafka и Propan, который хочет взять от них самое лучшее и создать единый способ для написания сервисов, работающих с очередями.

Третье, что показалось мне важным, разработчик Propan признаётся, что сделать унифицированный RPC для всех брокеров (например для Kafka) - задача сложная, особенно когда брокер by design плохо рассчитан на такую функциональность. Всё сводилось к тому, что тот самый простой высокоуровневый интерфейс плохо скрывал недостатки реализации, поэтому в FastStream от него отказались.
Ещё генерацию темплейтов удалили, теперь проект можно генерировать при помощи cookiecutter.

А что нового уже принёс FastStream?
В первую очередь, мне понравился механизм фильтров. Ещё добавили кастомную сериализацию - по умолчанию FastStream работает с Json, но если у вас Protobuf, Avro или msgpack - больше никаких проблем нет, можно добавить собственную логику для сериализации.
Отдельно хотел бы выделить систему мидлварей - теперь трассировку, логи, обогащение сообщений метаинформацией делать намного проще.

Очень надеюсь, что проект будет развиваться семимильными шагами, ведь идеи FastKafka и Propan мне очень сильно понравились, а сам Propan я очень часто рекомендовал знакомым.

P.S. Оказывается кастомная сериализация уже была.

#библиотека
Недавно пришлось писать конвертор который транслировал Markdown в довольно специфичный формат.

Была следующая идея - мне нужен парсер, который перегонит Markdown в синтаксическое дерево, которое я буду обходить.
С обычным python-markdown в этом плане банально неудобно работать - у него нет простого и расширяемого API (как минимум), который бы позволял решить мою задачу.

А вот у mistletoe такое API есть. Например, вот так выглядит рендерер markdown в jira:

class JiraRenderer(BaseRenderer):

def render_strong(self, token):
template = '*{}*'
return template.format(self.render_inner(token))

def render_emphasis(self, token):
template = '_{}_'
return template.format(self.render_inner(token))

Ещё mistletoe можно использовать как утилиту. Например, вот так markdown-файл можно перевести в тот же формат Jira:

mistletoe foo.md --renderer mistletoe.contrib.jira_renderer.JiraRenderer

Поэтому, если вам нужна небольшая, но расширяемая библиотека для работы с markdown - берите её, не прогадаете.

#библиотека
Коробка с питоном
Если обычного itertools вам мало, то можно использовать more-itertools. Эта библиотека добавляет огромное количество функций для работы с итераторами. На практике всеми ими не всегда пользуются, поэтому я выделю некоторые из тех, которые сам использую часто.…
На сегодня расскажу ещё пару рецептов с more_itertools.

1) map_if работает как обычный map, но применяет функцию на элемент только если оно попадает под условие. Например, вот так мы можем возвести в квадрат только те числа, которые делятся на 2 нацело:

example = [1, 2, 3, 4, 5, 6, 7, 8]
list(map_if(example, lambda x: x % 2 == 0, lambda x: x * x)) # [1, 4, 3, 16, 5, 36, 7, 64]


2) Получить последний элемент можно при помощи last. Возникает вопрос а зачем он существует, если можно указать sequence[-1]? Ответом является то, что last позволяет указать, что ему возвращать, если элементов в коллекции нет:

last([1, 2, 3]) # Очевидно получим 3 
last([], 0) # Список пустой, но получим 0
[][-1] # Получим IndexError


Ещё есть first - как понятно из названия, он получает первый элемент.

3) map_except тоже работает как map, но умеет игнорировать ошибки. Например, мы хотим получить только те элементы, которые получилось привести к целому числу:

example = [1, "1", "2", "test", "three", object, 4.0]
list(map_except(int, example, ValueError, TypeError)) # [1, 1, 2, 4]


4) Ну и в конце про take - он просто берет N элементов из итерируемого объекта:

example = range(10)
take(3, example) # [0, 1, 2]
take(20, example) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - если больше, возьмет доступные


#itertools #more_itertools #библиотека #рецепт
В одном чатике встретил проект fakeredis, реализующий Redis-сервер на питоне, который можно использовать для тестов. Очевидно, чтобы не таскать для тестов настоящий Redis.

Поддерживаются практически все стандартные команды, которые я встречал для работы с типами/коллекциями (список здесь). Есть не полная поддержка JSON, поиска, time series.
Если каких-то команд не хватает, автор оставил гайд, как её сделать. Ну а если сделаете - законтрибутьте в опенсорс, полезное дело всё таки!

#библиотека
Автор Pydantic совершенно недавно начал разрабатывать ещё один проект... И на этот раз он решил сделать UI фронтенд-фреимворк который назвал FastUI.

Если отвечать на вопрос "зачем", то в readme изложены вот такие поинты:
1) Если не хочется разбираться в JS/фронтенд фреимворках и всей их экосистеме;
2) Если хочется декларативно писать фронтенд на питоне, с переиспользуемыми компонентами;
3) FastUI - противоположность GraphQL, но у них одинаковые цели: когда GraphQL позволяет расширять фронтенд без расширения бекенда, FastUI предлагает расширять бекенд без разработки фронтенда;

Под капотом у нас:
- Сам пакет FastUI, который содержит утилиты и сами компоненты. Cами компоненты описаны с использованием Pydantic.
- Так как это всё это работает на react, есть npm пакет @pydantic/fastui, который позволяет повторно использовать механизмы и типы FastUI при реализации собственных компонентов.
- Bootstrap компоненты для FastUI.
- @pydantic/fastui-prebuilt который предоставляет пребилд основной части фронтенда.

Вкратце, как это работает - при помощи Pydantic моделей описываются интерфейсы React компонентов, а дальше их описания отправляются на фронтенд, где по ним отрисовываются компоненты.

Само описание небольшой страницы выглядит как-то так:

c.Page(
components=[
c.Heading(text=user.name, level=2),
c.Link(components=[c.Text(text='Back')], on_click=BackEvent()),
c.Details(data=user),
]
)


Простенько, лаконично, декларативно. Но такой же подход использует Flet, Dash и ещё куча других фреимворков. Очень интересно, взлетит ли это или нет?

Забавно, что мы не кодим на Rust, но всё у нас "fast" - FastAPI, FastUI, FastStream :D

#библиотека
С наступившим 2024 вас! Сегодня расскажу про dirty-equals.

Этот пакет используется для реализации более читаемых и декларативных проверок на равенство/соответствие, но в полной мере он себя раскрывает при написании тестов.
Как обычно, приведу несколько примеров:

from dirty_equals import *

>>> assert 1 == IsPositive # Всё ок, число положительное
>>> assert -2 == IsPositive # AssertionError
>>> # Проверки можно комбинировать при помощи булевой логики
>>> assert ['a', 'b', 'c'] == HasLen(3) & Contains('a') # Ок, список на 3 элемента, содержит 'a'
>>> # Есть проверка словарей
>>> assert {'a': 1, 'b': 2} == IsDict(a=1, b=2)
>>> # Словари можно проверять частично
>>> assert {'a': 1, 'b': 2, 'c': 3} == IsDict(a=1, b=2).settings(partial=True)
>>> # Или вот так
>>> assert {'a': 1, 'b': 2, 'c': 3} == IsPartialDict(a=1, b=2)
>>> Можно проверять соответствие регуляркам
>>> assert 'test@kiriha.ru' == IsStr(regex=r'^[^@]+@[^@]+\.[^@]+$')
>>> # И так далее...


Рекомендую заглянуть в документацию, там таких удобных штук ещё очень много! Ну и изучите исходники - написано не сложно, под капотом используется чуток метаклассов, для изучения темы - самое то!

#библиотека