Python Дайджест собирает IT-новости уже 9 лет, рассказывает о концепциях, проектах, релизах. Кодовая база за это время мало изменилась и уже деградировала. Более 5 лет не хватало сил и времени, чтобы привести проект в актуальное состояние. Django с 1.9 обновилась уже до 4.1 версии, Python 3.4 не актуален, да даже обновить пакет через pip не получается, потому что сломан.


В 4 частях расскажу от первого лица, как 9-летний проект из состояния outdated вернулся в actual состояние и снова набрал 100 баллов в PageSpeed.


Начну с обновления до актуального Python и Django.


Содержание цикла статей


  • 1 часть: Как обновиться с Python 3.4 до Python 3.11, если pip уже сломан
    • — вы здесь —
  • 2 часть: Как актуализировать всю кодовую базу с помощью pre-commit
  • 3 часть: Как сделать CI для OpenSource проекта с Github Actions
  • 4 часть: Как ускорить Django проект до (почти) максимума

О Python Дайджест




Python Дайджест — это open source проект про Python, написанный на Python. В следующем году исполнится 10 лет как собираются, вычищаются, изучаются материалы и собираются дайджесты.


Проект был создан разработчиками и для разработчиков. Хотелось знать актуальные подходы к разработке, библиотеки, обновления важных библиотек, узнавать, что теперь стало стандартом. И с того времени мало что поменялось. И все также я использую этот ресурс, чтобы быть «в курсе», только теперь не для себя, а для команд, с которыми работаю.


Состояние на конец ноября 2022:


  • Есть сервер на Ubuntu 14.04 с запущенным через uwsgi и supervisor Django приложением.
  • Основное virtualenv окружение с версией Python 3.4, но когда-то pip был обновлен руками на основе 3.6 версии и теперь не работает pip freeze.
  • Дополнительный virtualenv с версией Python 3.6, в котором работает часть приложения, но зависимости не совпадают с requirements.txt.
  • Как сконфигурирован сервер, приложение, переменные окружения и прочее — никто уже не помнит, а в bash history ничего нет.
  • Изменения в код вносились прямо на сервере и не дублировались в git.
  • Импорт новостей сбоит — системные SSL сертификаты устарели и уже не получается их обновить.

Задача (часть 1) — обновиться минимум до Python 3.6


3.6 — реперная точка, потому что там появились f-string. Эту функциональность нельзя включить через какой-нибудь __future__ flag на более старых версиях. При этом свежие версии библиотек почти всегда используют f-string.

План работ


  • Объединить код сервиса с сервера и git ветки.
  • Развернуть окружение на Python 3.4, чтобы запустить тесты локально.
  • Обновить зависимости и перейти на poetry для управления ими.
  • Постепенно обновлять зависимости и интерпретатор, чтобы получить Python 3.11 и Django 4.1.

Как синхронизировал кодовую базу


Для начала объединил текущую мастер-ветку с актуальным серверным кодом:


  • Заархивировал в tar.gz и перенес код с сервера на локальную машину с помощью scp.
  • Через meld (инструмент для визуального diff) просмотрел изменения и объединил их.
  • Сохранил в отдельной ветке результат объединения.

При объединении двух версий кодовых баз стоит внимательнее смотреть на:


  • Тесты. Как изменились тесты из репозитория, что теперь проверяют.
  • Миграции. Были ли изменения базы данных и какие. Бэкапом их повторим, а миграции нужно добавить в репозиторий.
  • Настройки и переменные окружения, в том числе и как обрабатываются типы переменных.
  • Как используются новые версии библиотек, чтобы учесть изменения их API.

Как НЕ запустил Python 3.4 на Ubuntu 22.04


У меня в системе стоит Python 3.10, что много выше, чем Python 3.4, который требуется для запуска приложения.


К счастью, есть pyenv, который позволяет установить любую версию python рядом с основной.


Это так я думал, но при выполнении команды pyenv intall 3.4 получил ошибку Missing the OpenSSL lib?. Даже инструкция про это есть.


Из инструкции и Pull Request становится ясно, что за время с Python 3.4 OpenSSL обновился с 1.11 до 3.0 (перепрыгнув несколько версий) и просто так на современном дистрибутиве его не поставить. Инструкция предлагает попробовать с системным OpenSSL версии 3.0, вместо 1.11, сделать downgrade версии, через brew на Ubuntu поставить нужную версию OpenSSL.


Ничего не дало плодов. Мне нужен был 3.4 и 3.6 Python, только пока зависимости обновляю, и поднимать полноценную виртуалку не хотелось. Поэтому решил пойти по другому пути.


Как запустил Python 3.4 в Docker


Если нужен какой-то софт, который тяжело поставить/не хочется ставить в систему, то что берем? Конечно, же контейнеры. В моем случае это Docker.


Контейнеры предоставляют условно изолированную среду внутри родительской операционной системы. Можно описать образ контейнера в файле Dockerfile со всем необходимым, загрузить на Docker Hub, а затем запускать единообразным способом на разных машинах.


В Docker Hub хранятся настолько древние образы, что можно удивляться для кого они. Вот для таких проектов, которые много лет работали и не требовали критических изменений.

Составил Dockerfile с Python 3.4, который умеет стягивать зависимости из папки проекта и устанавливать их. Вуаля! Теперь можно работать с зависимостями. А если указывать команду для запуска контейнера через docker compose (внутри dev ужас), будет совсем приятно и не надо будет пересобирать слишком часто.


На этом шаге стало ясно, что проект получится актуализировать.


Как получил версии пакетов со сломанным pip


Есть контейнер с Python 3.4. Теперь нужно получить актуальный серверный requirements.txt. Как писал в начале — pip был сломан из-за ручного обновления (python setup.py install для pip), но не беда — можно из интерпретатора достать.


import module_name

help(module_name)

И оттуда вычитать версию пакета (или просто grep'ом парсить из venv папки).
Это нужно было сделать для всех критических пакетов, чтобы, когда будет обновление зависимостей через poetry, получили рабочее приложение.


Какие использовал менеджеры зависимостей


Немного про менеджеры зависимостей.


На практике я проходил по пути от простого pip install, затем перешел к requirements.txt, где были описаны пакеты, затем для управления зависимостей использовал pip-tools (тогда poetry еще тестировался), а уже затем стал использовать poetry:


  • requirements.txt — текстовый файл, в котором по строчкам перечисляются пакеты и версии для них. Первый общеупотребимый подход по описанию зависимостей.
  • pip-tools — набор утилит, которые позволяют задать для основных зависимостей версии, а версии для остальных зависимостей вычислить автоматически. Это такой переходный инструмент от состояния «не фиксируем версии» к «фиксируем основное, а остальное не знаем надо ли». Зачастую работает, но бывает получается битое окружение.
  • poetry — это текущий стандарт управления зависимостями. Позволяет уже не бояться за окружение (зачастую) и вычислять версии зависимости так, чтобы все пакеты были точно согласованы.

Вероятно следующий будет pdm, а пока «стандарт» это poetry.


Pipenv с Pipfile.lock тоже использовал для управления зависимостями, хотя его мощь именно в создании всего dev окружения, а не только установка пакетов.

Как обновил Python зависимости с Docker



Image: Alice Lang, alicelang-creations@outlook.fr


Запуская через docker compose контейнер с Python 3.4, я начал приводить в порядок requirements.txt: указал актуальные версии с сервера, дописал недостающие в файл.


Это позволило запустить pip-tools (а точнее, pip-compile) и актуализировать все остальные зависимости.


Поставил poetry и последовательно добавил все пакеты в проект. Получил lock файл с хэшами пакетов. Фуф, теперь можно обновлять зависимости дальше — через poetry add "package_name<=version"


Обычно для пакетов назначают версию: «не обновляй выше текущей версии», однако, для массового обновления, где ручным образом проверяются, стоит ставить «меньше, равно». Уже после обновления можно заменить >= на ^

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


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


Требовалось ходить по пакетам, смотреть их Changelog и принимать решение.


Крупные пакеты типа lxml, pandas, numpy приходилось по несколько раз обновлять, а затем снижать версию обратно. Не всегда потребители этих библиотек учитывают изменения в API.

Дальше было несколько итераций обновлений согласно таблицы совместимости Python и Django версий


  1. Python 3.4 + Django 1.9
  2. Python 3.4 + Django 1.11
  3. Python 3.4 + Django 2.0.13
    • Переход на from django.urls import path для описания urls.py
    • Добавление on_delete= для ForeignKey/OneToOneField полей моделей
    • Переименование MIDDLEWARE_CLASSES на MIDDLEWARE
    • Здесь инструкция
  4. Python 3.6 + Django 2.0.13
    • Переход на f-string
  5. Python 3.6 + Django 2.2
  6. Python 3.6 + Django 3.2.16
    • Удаление from django.utils.six import text_type
  7. Python 3.10 + Django 3.2.16
    • Разделение на группы зависимостей в poetry (основная, для разработки, для тестов)
  8. Python 3.10 + Django 4.1
    • Переименование from django.utils.translation import ugettext_lazy -> gettext_lazy
  9. Python 3.11 + Django 4.1

Собирал образ с новым сочетанием зависимостей, прогонял тесты, удалял/добавлял пакеты, модифицировал код под версию Django, учитывая deprecations, обновлял зависимости, экспортировал новые зависимости. И так по кругу.


По итогу был получен образ с Python 3.11 + Django 4.1, который легко было запустить вне контейнера.


Через pyenv было поднято окружение с Python 3.11, прогнаны тесты еще раз.
Теперь можно запускать проект на актуальных операционных системах.




Выводы


Вы прочитали 1 из 4 часть цикла статей про обновление проекта до актуальных технологий. Для себя сделал такие выводы:


  • Для проектов на Python 3.4, 3.5, 3.6 через pyenv не всегда удается развернуть окружение. Для этого можно использовать Docker контейнеры. Собирать образ, поднимать контейнер с синхронизированной папкой исходного кода, из контейнера запускать pip install и так проводить процедуру обновления.
  • С установкой poetry (даже по схеме requirements.txt -> pip-tools -> poetry) заметно упрощается поддержка зависимостей. Poetry в пару команд позволяет выбирать актуальные зависимости для другой версии python, и сами зависимости держать в порядке.
  • Вот до чего может довести «работает — не трогай» :)

P.S. Актуализация существующего проекта весьма затратна по времени. Обращайтесь ко мне, axsapronov, если вам требуется сделать это.




НЛО прилетело и оставило здесь промокод для читателей нашего блога:
15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (5)


  1. Mingun
    31.01.2023 17:11

    Обычно для пакетов назначают версию: «не обновляй выше текущей версии», однако, для массового обновления, где ручным образом проверяются, стоит ставить «меньше, равно». Уже после обновления можно заменить >= на ^

    Что-то не совсем понял. Так всё-таки «меньше или равно» или «больше или равно»?


    1. axsapronov Автор
      31.01.2023 18:04
      +1

      Сначала неизвестно какие версии поддерживают необходимый Python. Обычно при повышении версии пакетов обрезают старые версии Python.
      Поэтому пока идет ручное обновление и просмотр changelog ставил <=. Тем самым контролировал что не сломается лишнего.

      Когда до нужной версии Python поднял, но можно отбросить "старые" версии пакетов и наоборот ставится >=. И обновляются все пакеты. Если что-то сломалось - то индивидуально эти версии меняются на <=


  1. onegreyonewhite
    31.01.2023 22:06
    +5

    А можно сразу 4ую часть? Пока что больше вопросов чем ответов. Но ко всему, я бы прошёлся и прокомментировал всё что могло бы вызвать вопросы. Хотя если задачка разовая, то это уже на совести и усмотрении вашем.

    1. Зачем было обновлять django пошагово? Есть changelog, в котором очень подробно пишется что дропнули и добавили + судя по всему есть тесты. В чём профит от этого геморроя?

    2. Зачем обновляться на не-LTS джангу? Одна из причин, почему хочется часть 4 посмотреть.

    3. Почему не использовали репозиторий с мёртвыми змеями? Питон 3.6 вполне себе там живой. Нее, контейнер тоже неплохо, но почему нет?

    4. Не понял проблему с pip. Он не запускался? Можно же в любой момент установить его скриптом get-pip. Ни разу не подводил.

    5. Почему не пользуетесь ~= наверное уже лишним будет, потому что poetry. Но какую проблему им решали? Чем pip не угодил?


  1. abdal_karim
    02.02.2023 05:44

    Здравствуйте, я только начал изучать python

    учусь по книге Toni Gaddisa 800+ стр, он там все разжовывает

    Дошел до этого момента

    Подскажите пожалуйста хватит ли мне этого набора знаний чтобы начать изучать django?


    1. MountainGoat
      02.02.2023 10:23

      Если уж так начали, то о классах и наследовании лучше сначала получить представление. А вообще я видел курсы, где наоборот: показывают основы Питона на примерах на Django.