Рупитон – Telegram
По следам вопроса в чате: как раздавать контент (картинки, документы, JS), и как это делать приватно

В большинстве популярных веб-фреймворков для Python есть опция раздачи статических файлов. И всегда в документации указано, что эта функциональность — для разработки и отладки, но не для продакшн-решения. А как выглядит продакшн-решение?

Простой уровень — настроить для этого веб-сервер (nginx, traefik, ...). Он будет раздавать статику быстрее, чем веб-фреймворк на Python, не будет тормозить API-запросы и позаботится о многих нюансах (сжатии, HTTP-хедерах для кэширования, рейтлимитах и проч.). Это годится для раздачи статики — JS или картинок, входящих в дизайн сайта.

Следующий уровень начинается, когда нагрузка на сервер большая, распределённая географически, и/или когда у нас появляется UGC (user-generated content), т.е. пользователи сами начинают заливать нам картинки, PDF-документы и прочие файлы в приличном объёме. В таком случае используют CDN (content delivery network) — Amazon S3, его аналоги (есть практически во всех облаках) и продвинутые обёртки для высокой нагрузки. CDN занимается за нас кэшированием (продвинутые сервисы даже могут раскопировать файлы по датацентрам в нескольких регионах для более быстрой раздачи), горизонтальным масштабированием, отказоустойчивостью и так далее.

При этом использование CDN, как правило, крайне дёшево (ставка порядка $0.02/GB в месяц означает, что даже когда ваши пользователи зальют терабайт данных, это будет обходиться всего в $20 в месяц) и очень просто — все облака предоставляют унифицированное S3-подобное API и, соответственно, могут быть подключены посредством одной библиотеки boto3).

Следующий вопрос — что делать, если контент приватный, например, паспорта, контракты и прочие документы, и не должен попадать в руки неавторизованных пользователей. Тут возникает два больших риска:
— ссылка на документ утечёт в интернет (например, её проиндексирует поисковик) и злоумышленник извне сможет получить доступ к документу;
— пользователь с доступом к одному документу займётся обходом адресов и получит несанкционированный доступ к другим документам.

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

Вторая проблема при этом перестаёт быть актуальной, но чтобы перестраховаться на случай случайного изменения настроек приватности, общая рекомендация — использовать UUID (или ObjectId или другие строковые идентификаторы) вместо номеров для приватных файлов. Эта полумера также может быть использована без временных ссылок для не-очень-но-немного-приватного контента; например, в соцсетях фотография может быть защищена настройками приватности, но по прямой ссылке картинка всегда доступна — потому что генерировать приватную ссылку для каждой картинки в зависимости от пользовательских настроек приватности было бы слишком накладно; при этом перебирать адреса и смотреть чужие приватные фото практически невозможно.

—————

Мы расширяем тематику канала и ищем авторов. Если вы хотите поделиться библиотекой, новостью или просто хорошим техническим материалом, пишите @saluev или любому другому из админов!
👍8❤‍🔥1🤗1😎1
По следам обсуждения в чате: сравнение перфоманса фреймворков

Всякий раз, когда релизится Новый Самый Быстрый Веб-Фреймворк на Python, релиз сопровождается красивыми картинками, на которых миллион RPS (requests per second, запросов в секунду) нового фреймворка сравнивается с полумиллионом RPS предыдущего Самого Быстрого фреймворка. После чего можно часто услышать тезис «X вдвое быстрее Y» при обсуждении технических решений.

Вдвое большее RPS в бенчмарке, конечно, не означает ни что проект будет отвечать в два раза быстрее, ни что проект будет держать вдвое бо́льшую нагрузку. По ряду причин, и в первую очередь из-за закона Амдала.

Закон Амдала изначально был сформулирован, чтобы оценить, насколько увеличение числа параллельных процессоров может ускорить программу, но может использоваться и для анализа ускорения за счёт любых других способов ускорить отдельные части. Закон формулируется так. Если мы используем какой-то способ ускорить программу (в нашем случае — замену веб-фреймворка), то у нас есть процент X времени работы программы, приходящийся на ускоряемый код (в нашем случае — на обработку запроса фреймворком) и процент Y = 1 - X времени, приходящийся на весь остальной код (в нашем случае — на бизнес-логику, включая походы в базу данных). Если мы ускоряем веб-фреймворк в N раз, то общее ускорение всего проекта получается порядка 1 / (Y + X / N). Например, если мы ускорили веб-фреймворк в два раза, но обработка запроса занимает 5% времени, то суммарное ускорение будет ~2.5%.

На практике время, занимаемое веб-фреймворком, невелико по сравнению с временем, уходящим на бизнес-логику, даже в простых проектах. В бенчмарке ASGI-фреймворков они демонстрируют латенси порядка 5 мс; это сопоставимо с одним запросом в базу данных — в сложном высоконагруженном сервисе их наверняка будет больше.

При принятии решения, на каком фреймворке писать проект, следует учесть не только его показатели в бенчмарках, но и не менее значимые факторы:
зрелость фреймворка и комьюнити — пройдены ли все грабли, существует ли активное ядро меинтейнеров, исчерпывающа ли документация и насыщен ли знаниями StackOverflow;
экспертиза — знают ли разработчики в вашей команде этот фреймворк (или хотя бы ближайшие аналоги) достаточно хорошо;
рыночная востребованность — легко ли нанимать разработчиков с опытом в этом фреймворке и желании писать на нём (и будет ли легко через несколько лет).

─────

Мы расширяем тематику канала и ищем авторов. Если вы хотите поделиться библиотекой, новостью или просто хорошим техническим материалом, пишите @saluev или любому другому из админов!
👍51
Паттерны работы с базами данных

В большинстве проектов мы храним какие-то данные. Для этого используются разные виды баз данных: реляционные, nosql или даже специализированные HTTP API. Такие хранилища имеют специфическое API, которое мы обычно хотим скрыть от основного кода за некоторой абстракцией. Вот стандартные варианты, описанные, в частности, Мартином Фаулером.

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

DAO - наиболее простой вариант, он представляет собой достаточно тупой класс, который просто выполняет операции с хранилищем и возвращает данные в том или ином виде. Он не должен содержать какого-то своего состояния (будь то кэши или IdentityMap). Он получает и возвращает только данные в виде неких абстрактных RecordSet или простых DTO, то есть структур, не содержащих логики. Плюсы такого паттерна: простота реализации, возможность точечного тюнинга запросов. Паттерн описан в "Core J2EE Patterns", а у Фаулера встречается очень близкое описание под именем Table Data Gateway.

Data Mapper - в отличие от DAO занимается не просто передачей данных, а двусторонней синхронизацией моделей бизнес логики с хранилищем. То есть он может получать какие-то сущности и потом сохранять их обратно. Внутри он может содержать IdentityMap для исключения дублей модели с одним identity или создания лишних запросов на загрузку. Каждый маппер работает с моделью определенного типа, но в случае составных моделей он иногда может обращаться к другим мапперам (например, при использовании select-in load). При использовании Unit Of Work, тот обращается именно к мапперу для сохранения данных.

Repository - фактически вариант Data Mapper, предназначенный для работы с корневыми сущностями. Для прикладной бизнес логики репозиторий выглядит как коллекция, содержащая корни агрегатов. Он может использоваться для получения полиморфных моделей, а также может возвращать некоторую сводно-статистическую информацию (например, количество элементов или сумму полей) или даже выполнять какие-то расчеты, не выходящие за пределы общей компетенции хранилища данных. Это основной паттерн при использовании богатых доменных моделей. Паттерн описан у Эрика Эванса, а у Фаулера встречаются некоторые варианты его реализации.

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

Raw Data Gateway - предлагает каждой строке таблицы поставить в соответствие экземпляр класса. Мы получаем отдельный класс Finder для загрузки строк и собственно класс шлюза строки, который предоставляет доступ к загруженным данным и обладает методами сохранения себя в БД.

Active Record - вариант RDG, но содержащий бизнес логику. По факту, мы имеем богатые доменные модели не абстрагированные от хранилища. Часто методы загрузки данных реализованы просто как static-методы в этом же классе вместо выделения отдельного Finder.

Строит отметить, что многие ORM в Python реализуют Active Record и активно используют при этом неявный контроль соединений и транзакций. В отличие от них SQLAlchemy реализует паттерн Data Mapper и может дать больший уровень абстракции над хранилищем (обратите внимание на подход с map_imperatively).

Дополнительные материалы:
http://www.corej2eepatterns.com/Patterns2ndEd/DataAccessObject.htm
https://martinfowler.com/eaaCatalog/identityMap.html
https://docs.sqlalchemy.org/en/20/orm/dataclasses.html#applying-orm-mappings-to-an-existing-dataclass-legacy-dataclass-use
🔥4👍1
Статья: как увеличить скорость python-скриптов: C-расширения и Python/C API

Известно, что зачастую, когда перфоманса Python не хватает (то есть почти всегда), можно ускорять отдельные участки кода за счёт переписывания их на C. Так, например, сделан NumPy и многие другие вычислительные пакеты. В свежевышедшей статье-туториале автор детально разбирает, как устроен пакет с C-кодом, как организовать сборку и как выглядит Python C API, с практическими примерами и бенчмарками. Рекомендуется к ознакомлению всем, кто выбрал Python как свой основной язык программирования.

Читать целиком на Хабре: https://habr.com/ru/companies/timeweb/articles/875420/
1👍1
float и Decimal

Вас никогда не удивляло, что 0.1 + 0.2 != 0.3? Почему float считает с погрешностями, и всем норм?

Дело в том, что 0.1 выглядит как

0 0111111101 11001100110011001100110011001100110011001100110011010.

Где:
0 обозначает знак +1 обозначает -)
0111111101 обозначает exponent, равную 0^10 + 2^9 + 2^8 + 2^7 + 2^6 + итд = 1019. Вычтем 1023 (размерность double) и получим итоговое значение: 1019 - 1023 = 4
11001100110011001100110011001100110011001100110011010 обозначет "significand" или "мантису", которая равна: 2^-exp + 2^-exp-1 + 2^-exp-2 + итд ~= 0.1

Вот так мы можем примерно представить 0.1 в виде float. Примерно – потому что все вычисления идут с погрешностью. Мы можем проверить данное утверждение, добавив погрешность вручную:

>>> assert 0.1 + 2.220446049250313e-18 == 0.1

Значение внешне не изменилось при добавлении погрешности. Посмотрим на sys.float_info.epsilon, который устанавливает необходимый порог для минимальных отличий 1.0 от следующего float числа.

>>> import sys
>>> sys.float_info.epsilon
2.220446049250313e-16
>>> assert 1.0 + sys.float_info.epsilon > 1.0
>>> assert 1.0 + 2.220446049250313e-17 == 1.0 # число меньше epsilon

Как конкретно будет выглядеть 0.1? А вот тут нам уже поможет Decimal для отображения полного числа в десятичной системе:

>>> decimal.Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')

И вот ответ про 0.1 + 0.2, полное демо с битиками:

>>> decimal.Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> decimal.Decimal(0.2)
Decimal('0.200000000000000011102230246251565404236316680908203125')

>>> decimal.Decimal(0.1 + 0.2)
Decimal('0.3000000000000000444089209850062616169452667236328125')

>>> decimal.Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')

Числа не равны друг другу, потому что их разница больше предельной точности float. А сам Decimal может использовать любую точность под задачу.

>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 6
>>> Decimal(1) / Decimal(7)
Decimal('0.142857')

>>> getcontext().prec = 28
>>> Decimal(1) / Decimal(7)
Decimal('0.1428571428571428571428571429')

Но и Decimal не может в абсолютную точность, потому что есть в целом невыразимые в десятичной системе числа, такие как math.pi, , тд. С чем-то из них может помочь fractions.Fraction для большей точности, но от существования иррациональных чисел никуда не деться.

Почему всем норм, что у нас с float такие погрешности в вычислениях? Потому что во многих задачах абсолютная точность недостижима и не имеет смысла. Благодаря плавающей точке мы можем хранить как очень большие, так и очень маленькие числа без существенных затрат памяти. А ещё float - очень быстрый. В том числе за счет аппаратного ускорения.

» pyperf timeit -s 'a = 0.1; b = 0.2' 'a + b'
.....................
Mean +- std dev: 8.75 ns +- 0.2 ns

» pyperf timeit -s 'import decimal; a = decimal.Decimal("0.1"); b = decimal.Decimal("0.2")' 'a + b'
.....................
Mean +- std dev: 27.7 ns +- 0.1 ns

Разница в 3 раза.

Про то, как устроен float внутри – рассказывать не буду. У Никиты Соболева недавно было большое и подробное видео на тему внутреннего устройства float. У него действительно хороший технический контент, советую подписаться: @opensource_findings

Итого
Если у вас нет требований по работе именно с десятичной записью числа (как, например, в бухгалтерии), то используйте float. Он даст достаточную точность и хорошую скорость. Если вы хотите, чтобы расчеты велись в десятичных цифрах и ваши расчеты построены так, что абсолютная точность достижима, то используйте Decimal.

Дополнительные материалы:
https://www.youtube.com/@sobolevn/
https://0.30000000000000004.com
https://en.wikipedia.org/wiki/X87
http://aco.ifmo.ru/el_books/numerical_methods/lectures/app_1.html
👍2
Статья: чему меня научила разработка продукта с нуля

Насколько сложно запустить мобильное приложение? Сколько микросервисов и строк кода нужно для прототипа? @saluev рассказывает, как делал бэкенд для стартапа в одиночку, что получилось и чему удалось научиться. Под катом даже есть некоторое количество кода на питоне.

Читать целиком на Хабре: https://habr.com/ru/articles/889758/
👍6❤‍🔥11🔥1😨1
Статья: год с Dishka: какой он — модный DI-контейнер?

@bomzheg рассказывает, как он использует Dishka в своём open source-проекте для городской игры. Код до, код после и рассказ про то, как работать с Dishka в e2e-тестах.

Читать целиком на Хабре: https://habr.com/ru/sandbox/241258
9❤‍🔥2
Статья: как я стал core-разработчиком Python в 19 лет

Как начать контрибьютить в опенсорс? Насколько быстро можно добиться результатов? @backinblacknext рассказывает, как он попал в команду разработки интерпретатора CPython — начиная с правок в документацию и заканчивая статусом core-разработчика со 138 смерженными пулл-реквестами.

Читать целиком на Хабре: https://habr.com/ru/articles/899636/
👍8❤‍🔥3🍌3🔥1
🔔 Relator от reagento (@smqwe12 и @Tishka17) — это GitHub Action, который позволяет отправлять уведомления о новых issues и PRs в телеграм.

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

Ключевые преимущества:
- Ограниченный scope токена для работы с вашими репозиториями
- Возможность переопределять шаблоны сообщений
- Полноценная поддержка форматирования текста

Быстрый старт:
- name: Send Telegram notification
uses: reagento/relator@v1.5.0
with:
tg-bot-token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
tg-chat-id: ${{ vars.TELEGRAM_CHAT_ID }}
github-token: ${{ secrets.GITHUB_TOKEN }}


Примеры использования:
FastStream
Dishka
wemake-python-styleguide

💻 Репозиторий
🚀 Группа
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍5👏2😁1
CayleyPy — open-source Python библиотека, применяющая методы искусственного интеллекта для решения задач теории графов и групп.

Если вы интересуетесь Python, графами или просто хотите поучаствовать в крутом научно-техническом проекте — присоединяйтесь! Математическая подготовка не обязательна — достаточно любопытства и желания внести вклад в open source. При активном участии Вы станете соавтором публикаций на А-стар конференциях. (Пинганите @alexander_v_c)

Основные особенности CayleyPy:

🧠 Использует методы искусственного интеллекта и обучения для исследования структур графов и групп
⚡️ Поддерживает генерацию и анализ Cayley-графов любой сложности
📊 Содержит инструменты для автоматического вывода математических гипотез
🔍 Полностью open source, с открытой документацией и активным сообществом
🧩 Простая архитектура — подключайтесь и экспериментируйте прямо из Python

💻 Репозиторий проекта
📚 Документация
🚀 RU Telegram
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🎄 Объявляем традиционный ежегодный ивент-ёлку.

С 1 по 12 декабря на Advent of Code выкладываются алгоритмические задачки, от простых до довольно сложных. Как участвовать: создайте аккаунт на Advent of Code (рекомендую через гитхаб) и вступайте в наш приватный лидерборд. Пароль для вступления:

83029-622626d1

Всем удачи!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
Рупитон pinned «🎄 Объявляем традиционный ежегодный ивент-ёлку. С 1 по 12 декабря на Advent of Code выкладываются алгоритмические задачки, от простых до довольно сложных. Как участвовать: создайте аккаунт на Advent of Code (рекомендую через гитхаб) и вступайте в наш приватный…»