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

Автор: @kiriharu
Download Telegram
Я всё чаще и чаще замечаю 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... Мне нравится такой подход, а вам?

#библиотека
FastAPI обновился до 0.89.0 и сделал фичу которой давно не хватало - наконец возвращаемый тип можно писать в аннотациях. Раньше, чтобы OpenAPI дока по нему сгенерировалась, надо было делать так:

@app.get("/", response_model=Model)
def endpoint():
return Model(name="Yurii")

Теперь же можем делать вот так:

@app.get("/")
def endpoint() -> Model:
return Model(name="Yurii")

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

#fastapi
Разговоры шли давно, но 9ого числа представили PEP 703, который предлагает сделать GIL опциональным. Прототип опубликован в репозитории nogil.

В качестве основания используется то, что Python используют для ML, а GIL - это серьезное препятствие для параллельных вычислений.
В этом PEP так же привели работу с PEP 684, который предлагает прекратить использовать GIL между несколькими интерпретаторами в процессе.

Как думаете, могут ли действительно убрать/сделать опциональным GIL?

#pep
В Python 3.12 планируют переделать f-строки в рамках PEP 701 – Syntactic formalization of f-strings.

Дело в том, что f-строки в язык занесли в версии 3.6, когда балом правил еще старый LL(1) парсер, который было сложно расширять, поэтому для f-строк был написан отдельный маленький парсер. Из-за этого они получились слегка неполноценными, а еще в этом отдельном парсере, само собой, были отдельные новые баги. Самое яркое ограничение — это то, что внутри f-строк нельзя использовать те же кавычки, в которых лежит сама строка. Это ж капец как неудобно, что приходится другие кавычки использовать, чтобы просто достать что-то из словаря. У меня линтер на это ругается, и приходится ему каждый раз объяснять, что тут красиво никак не сделать.

Вот, в 3.12 разбор f-строк доверят основному PEG-парсеру, а сам синтаксис f-строк станет частью грамматики языка. Можно будет использовать любые кавычки, которые нравятся. А еще бесконечно вложенные f-строки. Не знаю, зачем нам это, но хорошо, что станет можно. Жду 3.12.
Когда-то давно, когда я только изучал 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 #библиотека
Немного про роутинг в FastAPI

Если вы пользовались FastAPI, то наверняка знаете, что роут можно сделать либо асинхронным, либо синхронным. Так когда какой надо делать?

Скорее всего первая мысль которая придет вам в голову будет звучать как-то так - если у нас есть I/O-bound задачи (например работа с БД), то надо использовать асинхронщину, если всё остальное - потоки, процессы и так далее. Но тут есть несколько нюансов:

1) Под капотом FastAPI отлично справляется с обработкой как синхронных, так и асинхронных роутов. Если роут асинхронный, то задача по его обработке запустится в event loop, если синхронный - то в thread pool.
2) Так как синхронные роуты запускаются в thread pool, иногда просто нет вообще никакого смысла тащить в проект асинхронную ORM, так как всё и так будет работать не блокируя основное приложение.

Возьмем вот такой роут:
@router.get("/nonblocking-sync-operation")
def nonblocking_sync_operation():
time.sleep(10)
return {"test": "test"}

После того как мы перейдем по этому роуту, мы будем ждать 10 секунд и в конце получим ответ. При этом сам FastAPI не заблокируется, и сможет обрабатывать другие подключения - потому что функция запустилась в отдельном потоке.

А теперь возьмем вот такой роут:
@router.get("/blocking-sync-operation")
async def blocking_sync_operation():
time.sleep(10)
return {"test": "test"}

Здесь после перехода по роуту функция запустится в event loop и sleep заблокирует всё приложение до тех пор, пока он не пройдет. То есть, FastAPI вообще перестанет принимать подключения до тех пор, пока функция не выполнится.

Поэтому, если вам нужно написать на FastAPI небольшой CRUD и вы думаете тащить асинхронную ORM - задумайтесь, а надо ли она вам там вообще?

Ссылки:
- Path operation functions

#fastapi
В PEP 695 предлагают наконец переделать синтаксис для указания дженериков и ввести новый оператор для указания алиасов.

1) Что там с дженериками?
Например, если раньше было так:
_T_co = TypeVar("_T_co", covariant=True, bound=str)

class ClassA(Generic[_T_co]):
def method1(self) -> _T_co:
...

То сейчас предлагают сделать так:
class ClassA[T: str]:
def method1(self) -> T:
...

2) А что с алиасами?
Раньше алиасы типов записывались вот так:
_T = TypeVar("_T")
ListOrSet: TypeAlias = list[_T] | set[_T]
Сейчас предлагают сделать вот так:
type ListOrSet[T] = list[T] | set[T]


Если не ошибаюсь, то ждем в 3.12, PEP уже приняли.

#pep
Оказывается в конце апреля urllib3 обновили до 2.0.0, который делали аж с 2020 года. Из очень годного:

1) Сделали функцию urllib3.request(), прям как в всеми любимом requests. Теперь работать с ним можно следующим образом:

response = urllib3.request("GET", "https://example.com")
И никаких тебе больше urlopen.

2) Упростили работу с json, теперь все выглядит следующим образом:

resp = urllib3.request(
"POST", "https://httpbin.org/anything",
# Параметр json здесь заэнкодит json в body запроса
# и установит 'Content-Type' в 'application/json'.
json={"key": "value"}
)
# а вот так теперь можно получить наш жсончк c ответа
print(resp.json())

Всё как у requests, всё как у людей.

3) Тайпинги! Хотя, было бы странно видеть такого вида библиотеку без тайпингов в 2023 году.
Плюс ко всему команда описала, с какими проблемами они столкнулись когда добавляли тайпинги, почитать здесь.

Ссылка на новость
Docker 4.19 теперь поддерживает python в docker init

docker init - это утилита, которая упрощает добавление docker в проект. Она просто пробежит по вопросам, спросит версию проекта, какой командой он запускается и сгенерирует Dockerfile и compose-файл.

Мелочь, а приятно :)

#docker
Попался на глаза отличный перевод статьи "Writing Python like it's Rust", который скорее говорит о возможностях аннотаций типов и структур которые их используют (и еще немного про контекстные менеджеры).

Очень много примеров и очень много полезных советов :)

#статья #хабр
Если обычного 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 | Документация

#библиотека
Если у вас есть желание понять как работает asyncio, threading или multiprocessing, либо же появились вопросы - рекомендую обратить внимание на superfastpython.com

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

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

Практически каждая статья сопровождается реальными примерами, которые можно самому запустить.
Все материалы на английском, но некоторые из них переводит на русский комьюнити (например цикл статей про asyncio).

#asyncio #threading #multiprocessing #статья
Создание временных файлов

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

Для решения этих проблем в Python есть модуль tempfile. Нас интересует 2 функции - это TemporaryFile и NamedTemporaryFile.

TemporaryFile позволяет создать безымянный временный файл. Вот так можно создать временный текстовой файл, открыть его на запись и чтение (за это отвечает первый аргумент "w+t", подробнее можно прочитать здесь):

from tempfile import TemporaryFile
with TemporaryFile("w+t") as t:
t.write("Hello, boxwithpython!")
t.seek(0)
data = t.read()

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

from tempfile import 
NamedTemporaryFile
with NamedTemporaryFile("w+t") as t:
t.write("Hello, boxwithpython!")
print(t.name) # /tmp/tmpljhsktjt

#std