Это статья о том, как максимально скучно разрабатывать и выпускать приложения на Python. В прошлой статье я пояснил, что имею в виду под определением «скучный», давайте напомню: я не имею в виду «надежный», «без багов или «без инцидентов». Хотя некоторые из моих рекомендаций помогут уменьшить количество багов, я также хочу внести ясность: баги все равно будут встречаться. Вы будете продолжать сталкиваться с ситуациями, когда функция или, возможно, весь сервис не работает. «Скучное» для меня связано с источниками этих инцидентов. В предыдущем своем посте я говорил об управлении зависимостями и изложил подход, призванный (насколько это возможно) гарантировать, что это не станет источником неожиданных ошибок. Сегодня я хочу поговорить на тему «качества кода» — а именно об инструментах, которые помогают выявлять потенциальные ошибки и другие проблемы как можно раньше, в идеале еще до того, как они попадут в кодовую базу, не говоря уже о попадании в релиз. Небольшое уточнение, прежде чем мы пойдем дальше: я пишу с точки зрения человека, который в основном использует Python для бэкенд-разработки веб-приложений и других сетевых сервисов. В этом я разбираюсь лучше всего, поэтому статья будет с уклоном в эту сторону. Если вы используете Python для других целей, я надеюсь, что из этой статьи вы все-таки сможете почерпнуть что-то полезное, но я не обещаю, что вся информация для вас будет актуальной. Я не ставил целью перечислить в этой статье все инструменты — их слишком много. Вместо этого я расскажу о нескольких, которые я лично могу рекомендовать, вместе с объяснениями, почему я их советую. Если они вам не понравятся, есть другие способы достижения целей, которые я изложил здесь, их можно изучить отдельно. Если вас интересует только конечный результат, перейдите к последнему пункту статьи «Все рекомендации вкратце».  Начну с основ: контроль версий, тесты, непрерывная интеграция (CI). Если их до сих пор нет, считаю крайне важным внедрить их. Без контроля версий нет возможности отслеживать, что изменилось в коде с течением времени; без тестов нет способа узнать, правильно ли работает код; без CI не получится автоматически узнавать, если недавние изменения внесли ошибки в код. Все эти три пункта очень важны. Для контроля версий, возможно, стоит использовать git, как и большинство разработчиков. Для тестирования: выберите либо модуль unittest из стандартной библиотеки Python, либо сторонний тестовый фреймворк pytest. Я предпочитаю unittest. Все встроенные инструменты тестирования Django, например, построены на основе unittest. Однако, у pytest тоже много поклонников, и есть сторонние плагины для воссоздания инструментов тестирования Django поверх pytest. Для CI: я использовал много разных CI-систем на протяжении многих лет, и ни одна из них не показалась мне идеальной. Поэтому я не буду рекомендовать что-то конкретное, а просто скажу, что лучше иметь любой CI-инструмент, чем искать идеальный. Если вам никак не удается принять решение, вероятно, лучше просто перестать беспокоиться и пока остановиться на том, что встроено в вашу платформу для размещения кода (GitHub Actions, GitLab CI и т. д.). В экосистеме Python также есть инструменты, которые помогут организовать и оркестрировать автоматические проверки как для локального использования, так и для CI. Здесь я мог бы рекомендовать tox, но в данный момент я изучаю nox в качестве альтернативы, поэтому пока не могу дать однозначной рекомендации. Тестовым покрытием принято называть автоматическое измерение того, какой процент строк кода в фактической кодовой базе выполняется во время тестовых прогонов. Тема спорная. Приведем кратко мнения за и против: За: Измерение покрытия помогает обнаружить части кодовой базы, которые не тестируются. Если вы знаете, что во время тестовых прогонов выполняется весь код, вы должны быть уверены, что код делает то, что должен. Против: Измерения покрытия слишком легко «обыграть» — 100% покрытия можно достичь без осмысленного тестирования всего или даже большей части кода. Это хорошо иллюстрирует действие закона Гудхарта – «Когда показатель становится целью, он перестает быть хорошей мерой». Лично я не думаю, что 100% покрытие в качестве цели является хорошей идеей, но я все же считаю необходимым измерять процент покрытия во время тестовых прогонов. Вместо того, чтобы относиться к нему как к цели, я предпочитаю рассматривать его как инструмент предупреждения: я хочу знать, если процент покрытия внезапно упадет, потому что это послужит признаком того, что что-то пошло не так либо в основной кодовой базе, либо в тест-сьюте. Показатели покрытия позволяет собирать модуль coverage. Также есть плагин для pytest. Есть вещи, не привязанные конкретно к Python или какому-либо другому языку программирования, но все же чрезвычайно полезные. Первый — файл с исключениями в системе контроля версий. Этот файл называется .gitignore, если вы используете git, и .hgignore, если вы используете Mercurial, и другие имена для других инструментов управления версиями. GitHub поддерживает файл .gitignore для Python, который можно просто скопировать и использовать. Он убережет вас от случайных ошибочных коммитов, например, каталогов кэша, используемых во многих распространенных Python-инструментах и процессах. Если в рамках проекта создается Docker контейнер, также создайте файл .dockerignore, чтобы обозначить файлы и каталоги, которые следует исключить из контейнера. Следующим инструментом является EditorConfig — он настраивается путем создания файла с именем .editorconfig в корневом каталоге репозитория. Он используется, чтобы различным IDE/текстовым редакторам можно было сообщить основы работы с вашим проектом. Например, можно указать, должны ли в файлах быть отступы пробелами или табами, какой отступ должен использоваться на каждом уровне, какой стиль новой строки использовать и многое другое.  Обычно я начинаю с копии файла Django .editorconfig и удаляю ненужные части. Я рекомендую это сделать, так как добавление файла .editorconfig в проект позволит как минимум избежать какой-то части ручной настройки IDE/редакторов и получить некоторую автоматическую согласованность для всех, кто работает над проектом, даже если при этом используются разные IDE/редакторы. Последний инструмент, не зависящий от языка, который хорошо бы настроить, — это утилита pre-commit. Если вы используете git в качестве системы управления версиями, она позволяет подключать автоматические проверки каждый раз при создании коммита. Она также может либо автоматически исправить проблемы за вас, либо просто отклонить коммит и сообщить, что пошло не так.  Несколько инструментов, которые я порекомендую ниже, имеют доступные pre-commit хуки (обработчики, вызываемые перед выполнением коммита). Но для начала просто настройте pre-commit файл конфигурации и добавьте несколько встроенных хуков. Я рекомендую использовать как минимум следующие: check-added-large-files check-ast check-byte-order-marker check-case-conflict check-docstring-first check-executables-have-shebangs check-json check-merge-conflict check-toml check-yaml debug-statements detect-aws-credentials detect-private-key end-of-file-fixer requirements-txt-fixer trailing-whitespace Они будут отлавливать/исправлять множество распространенных проблем, от простых синтаксических ошибок до случайных коммитов конфиденциальных данных. Для проектов, в которых несколько человек работают по модели Branch Workflow, также полезна функция no-commit-to-branch — она позволяет указать список веток, в которые нельзя закоммитить напрямую (набор по умолчанию — main и master), поэтому никто случайно не закоммитит локально основную ветку репозитория вместо своей собственной рабочей ветки. Этот хук регулярно избавляет меня от необходимости отменять случайный локальный коммит в main, когда я забываю, на какой ветке находился. Может показаться странным рассматривать эту составляющую в качестве отсылки к «качеству», но, по моему опыту, одним из наиболее важных факторов качества кода является читабельность. Вам иногда нужно читать код ваших коллег, а им — ваш. Вам также часто приходится читать свой собственный код, написанный несколько месяцев или лет назад. Эту работу нужно облегчить за счет единообразного стиля и форматирования. Совет из предыдущего раздела помогает прийти к согласованности форматирования благодаря .editorconfig и хукам pre-commit, обрабатывающим отступы, стили новой строки и т. д. Теперь настало время перейти к форматированию, специфичному для Python. Я рекомендую использовать два инструмента вместе: Black и isort. Black может привести все виды синтаксических конструкций Python к единому стилю. Хорошо то, что он заканчивает все споры о форматировании кода, потому что Black намеренно не предоставляет особых возможностей для настройки своего стиля. Как есть, так и есть. Между тем, isort переформатирует блоки инструкций import Python, чтобы они соответствовали единому стилю и размещались в определенном порядке: сначала импортируются стандартные библиотеки, затем сторонние модули, затем модули вашего собственного кода. Единственный вариант конфигурации, который я рекомендую установить для Black, — это целевая версия Python, которая должна соответствовать версии Python, которую вы собираетесь использовать для релиза (для библиотек, предназначенных для нескольких версий Python, это более сложно, но для приложения, которое вы собираетесь выкатить на ваши собственные серверы, нужно использовать одну версию Python и ориентироваться на нее). Для isort я рекомендую установить для параметра «profile» значение «black», чтобы указать isort, чтобы он соответствовал предпочтениям Black. Если у него возникают проблемы с распознаванием, какие модули являются частью кодовой базы, а какие нет, рассмотрите возможность установки known_first_party и/или known_third_party. В обоих случаях я рекомендую поместить конфигурацию в файл верхнего уровня pyproject.toml в вашем репозитории. В случае Black это единственный поддерживаемый файл конфигурации, и большинство других инструментов теперь поддерживают использование pyproject.toml в качестве централизованного файла конфигурации. Из тех, что не поддерживают, порекомендовать можно только один — flake8.  Ряд рекомендаций касательно запуска с Black и isort: Любой редактор/IDE, который может настроить запуск Black и isort для автоматического переформатирования кода при каждом сохранении файла, должен быть на это настроен. Но на тот случай, если кто-то из вашей команды этого не сделает, настройте pre-commit хуки как для Black, так и для isort; они будут автоматически переформатировать файлы во время коммита. В CI и Black, и isort должны работать, но не переформатировать код. Оба инструмента поддерживают режим, в котором они просто завершаются с ненулевым кодом и выводят то, что нужно переформатировать. Это не решит все возможные проблемы с читабельностью кода, но по крайней мере, это хорошее начало. После форматирования кода самое время подумать о линтинге. Двумя самыми популярными линтерами в мире Python являются flake8 и Pylint. Между ними есть некоторые различия. Сильно упрощенная версия различий будет выглядеть так: Flake8, как правило, быстрее и вызывает меньше ложных срабатываний, но делает не так много видов проверок. Pylint, как правило, медленнее и будет иметь больше ложных срабатываний, но делает гораздо больше разных проверок. О Pylint необходимо знать следующее: он требует, чтобы все, включая все зависимости, было импортируемым во время выполнения (чтобы проверять правильное использование импортированных модулей/функций/классов). А также, если вы используйте библиотеку или фреймворк, в котором есть много метапрограммирования, вам, вероятно, понадобится плагин, чтобы помочь Pylint. Например, если вы используете Django, вы почти всегда будете использовать pylint-django. Мой совет: выберите и запустите хотя бы один – или flake8, или Pylint. Если вы выбрали Pylint и у вас уже есть большая кодовая база, постепенно включайте ее различные проверки. Собственная документация Pylint содержит объяснения и рекомендации, как это сделать. Хорошим гибридным вариантом является запуск Pylint как части пакета CI, а flake8 — в pre-commit: Pylint довольно сложно использовать из pre-commit хука, и он может быть медленным, в то время как у flake8 есть официальный быстрый pre-commit хук. Кроме того, если вы используете flake8 в любом качестве, я рекомендую включить плагин flake8-bugbear, который добавляет кучу полезных проверок поверх обычного пакета flake8. Также я настоятельно рекомендую еще один линтер – Bandit. Он фокусируется в основном на потенциальных проблемах безопасности и умеет проверять многие из них. Просто обязательно отключите проверку B101 (которая по умолчанию запрещает использование assert) при запуске юнит-тестов. К счастью, большинство программистов осознали ценность контроля версий и автоматизированного тестирования. Но к ведению документации, к сожалению, многие до сих пор по каким-то причинам относятся скептически. Не знаю, почему. Я люблю документацию и рассматриваю ее как неотъемлемую часть всех моих проектов. А у Python есть отличные инструменты для ее поддержания. Как минимум, стоит использовать способность Python встраивать документацию вместе с кодом через docstrings. Для этого я рекомендую использовать interrogate, который сообщит, есть ли модули, классы, методы или функции, которые не имеют docstrings. Но на самом деле, необходимо писать больше документации, чем просто документационные строки (хотя у вас все равно должны docstrings), и для этого следует использовать надлежащий инструмент. В мире Python это Sphinx. Если вы категорически отказываетесь использовать в качестве текстовой разметки что-либо, кроме Markdown, я рекомендую вам хотя бы попробовать родной reStructuredText Sphinx. Если он окажется слишком непривычным, можно использовать Sphinx с поддержкой Markdown через плагины. Одним из огромных преимуществ Sphinx является его богатая поддержка аннотаций и перекрестных ссылок, которая позволяет ссылаться не только на другие части вашей собственной кодовой базы, но и на другие кодовые базы, документированные Sphinx, включая сам Python (основной язык и стандартную библиотеку) и многие популярные фреймворки и пакеты, такие как Django. Также можно использовать плагин Sphinx autodoc для извлечения docstrings из кодовой базы, но имейте в виду, что документация должна быть больше, чем просто автоматически сгенерированная ссылка на API. Мои рекомендации для проверки документации: В pre-commit и CI запустите interrogate (уже упомянутый), чтобы обеспечить наличие docstrings. В CI запустите сборку Sphinx, чтобы убедиться, что сборка документации выполняется без ошибок. В CI используйте sphinxcontrib-spelling для проверки орфографии документации. Пометьте примеры кода в документации с помощью директивы doctest и в CI запустите подключаемый плагин Sphinx doctest, чтобы убедиться, что эти примеры кода работают. Последний пункт может быть спорным, но позвольте повторю: это не означает, что вы должны попытаться внедрить полный тест-сьют вашей кодовой базы в docstrings. Это говорит о том, что любые примеры, добавляемые в документацию, должны быть проверены на правильность, и именно для этого предназначен doctest. Если вы работаете над чем-то, что предполагается встроить в пакет и распространять, есть еще несколько полезных инструментов. Во-первых, следует установить пакет build и запустить python -m build, чтобы убедиться, что упаковка работает. build — это внешний интерфейс, который понимает множество различных механизмов сборки пакетов и может генерировать стандартные форматы пакетов Python (.tar.gz и .whl). Если python -m build  завершается неудачей, это явный признак того, что с упаковкой что-то не так. Затем я рекомендую запустить еще несколько проверок: check-manifest собирает пакет, сравнивает его содержимое с файлами, отслеживаемыми системой контроля версий, и выдает ошибку, если списки файлов не совпадают. Это невероятно полезно в тех случаях, когда вы добавили файлы в систему управления версиями, но забыли обновить конфигурацию пакета. check-wheel может предупредить о ряде распространенных проблем с пакетом, таких как пустые пакеты, случайное включение файлов байт-кода Python и других. pyroma присвоит пакету рейтинг на основе наличия или отсутствия ключевых значений метаданных пакета, таких как классификаторы/ключевые слова проекта, лицензия, поддерживаемые версии Python и т. д. Утилита twine check пакета twine предупредит, если описание пакета не будет отображаться должным образом в указателе пакетов Python. Если вы используете GitHub Actions в качестве CI, рассмотрите возможность использования build-and-inspect action для автоматического применения некоторых из них. Внимательный читатель мог заметить, что ни в одном из разделов этой статьи я не говорил об аннотациях типов и статических проверках типов. Я сделал это специально, поскольку не собираюсь рекомендовать запускать статическую проверку типов кода на Python. На данный момент доступно несколько инструментов проверки разной степени зрелости, попробовать можно любой из них. Мой личный подход заключается в добавлении аннотаций типов в код, когда это возможно: они полезны в качестве документации (но они не должны быть единственной документацией!), и их автоматически подхватывают как инструменты документации, так и многие редакторы/IDE. Но я не запускаю статическую проверку типов mypy по нескольким причинам. Во-первых, идиоматический код Python, как правило, ближе к структурно-типизированному или «интерфейсно-типизированному» концу спектра. Например, вас особо не волнует принадлежность данных к типу list, для вас важно то, можно выполнить перебор или индексировать его. Тем не менее, экосистема аннотаций типов Python с самого начала была ориентирована на номинальную типизацию (т. е. заботу о том, чтобы что-то было экземпляром/подклассом определенного именованного типа). Там дела пошли лучше, но поддержка Protocol (официальное название интерфейса) не появлялась в модуле typing стандартной библиотеки до версии Python 3.8. До этого приходилось либо использовать сторонние реализации, либо, что хуже, говорить модулю контроля типов «когда я говорю, что это реализует необходимый интерфейс, просто верь мне». Невозможно было определить, как выглядит интерфейс, и заставить модуль контроля типов его понять. Поправка: Некоторые недопонимают суть абзаца выше – они думают, что он просто о том, существовали ли такие вещи, как typing.Iterable. Это не так! На протяжении ранней истории модулей контроля типов Python, даже при указании «протокольного типа», таким как Iterable или Mapping, по-прежнему не было способа сообщить модулю контроля типов, что это означает на самом деле. Поэтому модули контроля типов могли только обеспечивать выполнение определенных действий, например, «является подклассом typing.Iterable» (что является подходом с номинальной типизацией), а не «реализует требуемые методы, чтобы они действительно были итерируемыми».  Так, например, можно написать подкласс Iterable без реализации необходимого интерфейса, и модуль контроля типов все равно пройдет, потому что его заботит только наличие Iterable где-то в родительских типах. Только в Python 3.8 и реализации PEP 544 экосистема аннотаций типов Python получила стандартизированный способ определения интерфейсов и их фактическую структурную реализацию с помощью модуля контроля типов. Если вам все еще трудно понять разницу, представьте, если бы Java изначально запускалась с проверкой «интерфейса», которая искала бы только спецификатор implements, содержащий правильное имя, а не соответствующий набор необходимых членов в самом классе. Python сделал примерно это, и мне это не нравится. И даже учитывая прогресс с протоколами, я думаю, что подсказки типов (type hints) все еще не полностью реализованы. Например, аннотирование функций высшего порядка, которые являются еще одной важной частью идиоматического Python, не в последнюю очередь в форме декораторов, по-прежнему так себе опыт, и хотя были предложения по его улучшению, для улучшений требуется время. Напомню, что я прежде всего веб-разработчик. Я в значительной степени полагаюсь на библиотеки и фреймворки — особенно ORM, такие как Django ORM или SQLAlchemy, — которые выполняют много метапрограммирования во время выполнения и, как результат, до сих пор не очень хорошии в проверке типов. Конечно, работа ведется, наблюдаю за прогрессом, но на мой взгляд, опыта пока недостаточно. Я рекомендую добавлять аннотации типов в свой код (я сам это делаю), но как только они появляются, лично я использую их только как документацию и в настоящее время не использую средство проверки статического типа. Вы же можете использовать статическую проверку, если хотите. Для некоторых инструментов, которые я порекомендовал, и для некоторых ситуаций, в которых они запускаются, не получится контролировать способ их вызова. Например, есть проекты, которые предоставляют свои собственные pre-commit хуки, решают, как они должны вызываться хуком, и весь код для этого находится в их репозитории. Но, по крайней мере, в вашем CI у вас есть над этим контроль, и я рекомендую проявлять надлежащую осторожность. Во-первых: хотя большинство инструментов, которые я рекомендовал, могут быть запущены как отдельная команда — например, black для средства форматирования кода Black — они также поддерживают запуск в виде модулей Python, например python -m black. И я рекомендую по возможности использовать второй способ запуска. Я упомянул об этом в прошлом своем посте, и у разработчика ядра Python Бретта Кэннона есть объяснение этого для конкретного случая pip. Общая идея такова: когда вы работаете с большим количеством установок/виртуальных сред Python, лучше быть как можно более конкретным в вызове, чтобы убедиться, что вы получаете тот Python, который вам нужен. Так, например, если вы хотите запустить что-то с помощью Python 3.9, предпочтительнее python3.9 -m <name of thing>. И мне лично нравится распространять этот принцип на все мои инструменты CI. Также Python можно передавать дополнительные флаги командной строки. Для CI, особенно в общедоступных/открытых проектах, хорошей идеей будет использование флага -I. «I» расшифровывается как «изолированный» (Isolated). Этот флаг удаляет некоторые неявные директории из пути импорта и игнорирует все переменные среды, которые настраивают/изменяют поведение Python. Это полезно, когда вы не доверяете всему, что можете запустить. Как пример, это может предотвратить следующую проблему: текущий рабочий каталог, как правило, неявно находится в пути импорта. Это означает, что вредоносный пул-реквест на включение может, предположим, поместить файл с тем же именем, что и модуль стандартной библиотеки, в каталог, из которого вы вызовите инструментарий CI, и этот модуль затем будет импортирован и запущен всем, что попытается импортировать модуль стандартной библиотеки с тем же именем. Запуск Python в изолированном режиме предотвращает подобное. Наконец, я рекомендую добавить еще один флаг командной строки для вызова Python, по крайней мере для тест-сьюта. Python поддерживает предупреждения как способ сигнализировать о вещах, которые не являются ошибками или, возможно, пока не являются ошибками, но о которых полезно знать. Одним из наиболее важных является DeprecationWarning, который выдается Python и многими сторонними библиотеками, чтобы предупреждать о том, что определенные API находятся в процессе устаревания и когда-нибудь будут удалены. Но по умолчанию Python не отображает предупреждения об устаревании, а значит, их легко пропустить. Если вы хотите узнать об устаревании как можно раньше, я рекомендую использовать флаг командной строки -W, чтобы изменить это поведение. В частности, передача -Wonce::DeprecationWarning покажет предупреждения об устаревании, но только в первый раз, когда каждое конкретное место в коде вызовет предупреждение (так что вы не будете засыпаны спамом, если ваш код часто вызывает устаревшие API). Поэтому, если, скажем, вы используете pytest для запуска тестов, я рекомендую не делать этого: Лучше следующее: Если вы используете инструмент автоматизации, такой как tox или nox, обычно существует какой-то метод получения конкретного интерпретатора Python для текущей среды — в tox замена {envpython} , а в nox — атрибут session.python (который возвращает версию number в виде строки, поэтому вы можете сконструировать правильный вызов интерпретатора как f"python{session.python}")). Напоследок приведу краткое изложение всех рекомендаций. Обязательно: контроль версий, юнит-тесты, автоматические тестовые прогоны/непрерывная интеграция. Включите отчеты о покрытии, но относитесь к ним как к инструменту предупреждения (если уровень покрытия внезапно упадет), а не как к цели. Настройте файлы с исключениями как для системы контроля версий, так и для Docker (если вы его используете). Настройте EditorConfig и выполните pre-commit. Запустите flake8 (с плагином flake8-bugbear) или Pylint в качестве линтера и запустите линтер безопасности Bandit. Документируйте код с помощью Sphinx. Используйте его плагин autodoc для извлечения документационных строк из кода. Используйте intersphinx для перекрестных ссылок на другие проекты (например, Python или используемые вами библиотеки/фреймворки), sphinxcontrib-spelling для проверки орфографии в документации и плагин Sphinx doctest, чтобы проверить правильность любого примера кода в документации. Обеспечьте наличие документационных строк во всем коде с помощью Interrogate. Если вы собираете пакет для распространения/установки, запустите сборку, проверку twine, check-manifest, check-wheel-contents и pyroma. Добавляйте аннотации типов в код только для того, чтобы использовать их в качестве документации. Запускать ли проверку статического типа и какую именно, решать вам. Что касается того, где использовать каждый инструмент: Попросите разработчиков из вашей команды настроить свой редактор/IDE (если возможно) на автоматический запуск средств форматирования кода Black и isort при каждом сохранении файла. Для pre-commit запустите: Black и isort, позволяя им переформатировать код Линтер flake8 Линтер безопасности Bandit Interrogate Список встроенных pre-commit хуков, перечисленных в статье. В CI вызывайте инструменты, явно называя интерпретатор Python для использования и вызывая инструмент как модуль с флагом -m (т. е. python3.10 -m pytest вместо просто pytest), переводя интерпретатор в «изолированный» режим с флагом -I. Для основного тест-сьюта запустите предупреждения об устаревании, включенные с помощью -Wonce::DeprecationWarning. А также запускайте следующие инструменты: Black и isort, но не позволяйте им переформатирование; вместо переформатирования они должны выдавать ошибку. flake8/Pylint Bandit Interrogate Сборка документации. Проверка корректности документации. Протестируйте каждый пример кода в документации с помощью плагина Sphinx doctest. Если вы собираете пакет, запустите проверки пакета:  python -m build, twine check, check-manifest, check-wheel-contents, pyroma. Спасибо, что дочитали до конца. В заключение статьи приглашаем всех желающих на открытое занятие по теме «схемы аутентификации», которое пройдет завтра вечером в рамках курса "Python Developer. Professional". На этом занятии мы обсудим различные способы аутентификации пользователей: basic, token (с jwt и без), OAuth2 на примере FastAPI приложения. Также скоро состоится открытый урок, на котором познакомимся с функциями-помощниками: map, filter, reduce. Узнаем, где они применяются и как их можно использовать. Записаться на урок можно на странице специализации "Python Developer".  Переводчик, редактор