Особенности pip и потенциальная дырка
Не вдаваясь конкретно в библиотеки (это два клиента для keycloak), расскажу о ситуации.
Есть библиотека А и библиотека B. Библиотеки А и B обе зарегистрированы в pypi под разными именами. Но в setup.py обе экспортируют одинаковые по имени пакеты (параметр packages), которые и буду в итоге отображены в вашем списке пакетов. Как вы думаете, как поступит pip, если указать ему обе библиотеки в зависимостях проекта?
... минутка на подумать ...
Он их смёржит между собой. При этом будет устанавливать одну библиотеку поверх другой, в порядке как в списке зависимостей. Я не нашёл ни в документации ни где-то ещё описания такого поведения. А оно приводит например к следующему:
- у вас в списке зависимостей библиотека A идёт перед библиотекой B,
- и там и там есть файл exceptions.py, в котором прописаны исключения,
- после установки библиотеки B, файл exceptions.py будет из библиотеки B, при этом уникальные для библиотеки А файлы так и останутся на месте и в импортах будет использоваться exceptions.py, который уже от другой библиотеки,
- как минимум это приводит к конфликтам, ошибкам и дебагу.
Ещё интересным выглядит вектор раскручивания этого до уязвимости: а что, если мы в нашей библиотеке (или в зависимостях нашей библиотеки) пропишем какое-то популярное имя пакета (например Flask) и переопределим поведение? Т.e. любой, кто установит нашу библиотеку не глядя на то, что внутри, имеет шанс запустить у себя наш код не подозревая об этом. Достаточно просто нашу библиотеку в списке зависимостей разместить ниже фреймворка, в который мы лезем.
Мем конечно смешной, а ситуация страшная.
Решил сходить и спросить у знающих людей, как так можно жить. Никита Воронов делает dephell и рассказал, что это не баг, а фича.
Так можно делать отдельные пакеты с плагинами, которые положат себя в папку plugins. Причём много кто это делает, так что поведение уже поздно менять. Про опасность пакетов вообще бесполезно говорить. Уже при установке пакет может в setup.py что угодно сделать. Например, слить твой ssh ключ. Так что недоверенные пакеты устанавливать вообще нельзя. Можно почитать, почему никогда нельзя звать sudo pip install. Проблема в том, что альтернатив нет, а все другие пакетные менеджеры (в том числе и dephell) всё равно внутри зовут pip.
Всё это грустно. Ну и надо думать над тем, как называть пакеты и смотреть, есть ли кто-то уже с таким же именем в pypi. Для решения моего конфликта оказалось проще руками затащить часть одной из либ в проект. Другой найденный вариант — использовать install-options pip’a и prefix для задания кастомного пути одной из либ.
Ну и конечно устанавливайте только доверенные пакеты.