python setuptools install_requires игнорируется при переопределении cmdclass

У меня есть setup.py, который выглядит так:

from setuptools import setup
from subprocess import call
from setuptools.command.install import install

class MyInstall(install):
    def run(self):
        call(["pip install -r requirements.txt --no-clean"], shell=True)
        install.run(self)

setup(
    author='Attila Zseder',
    version='0.1',
    name='entity_extractor',
    packages=['...'],
    install_requires=['DAWG', 'mrjob', 'cchardet'],
    package_dir={'': 'modules'},
    scripts=['...'],
    cmdclass={'install': MyInstall},
)

Мне нужно MyInstall, потому что я хочу установить некоторые библиотеки с github, и я не хочу использовать вариант dependency_links, потому что это не рекомендуется (например, здесь), поэтому я могу сделать это с помощью requirements.txt.

Когда я устанавливаю этот пакет с pip, все работает нормально, но по некоторым причинам я должен решить это так, чтобы он работал и с чистым python setup.py install. И это не так.

При переопределении cmdclass в setup() моим собственным классом install_requires, похоже, игнорируется. Как только я закомментирую эту строку, эти пакеты будут установлены.

Я знаю, что install_requires не поддерживается, например, в distutils (если я хорошо помню), но он есть в setuptools. И тогда cmdclass не окажет никакого влияния на install_requires.

Я часами гуглил эту проблему, нашел много похожих ответов на stackoverflow, но не для этой конкретной проблемы.

С размещением всех необходимых пакетов в файле requirements.txt все работает нормально, но я хотел бы понять, почему это происходит. Спасибо!


person zseder    schedule 20.02.2014    source источник


Ответы (3)


Та же проблема только что случилась со мной. Почему-то кажется, что что-то заставляет setuptools выполнить «установку в старом стиле» с distutils, которая действительно не поддерживает install_requires.

Вы вызываете install.run(self), который вызывает run(self) в setuptools/setuptools/command/install.py, строка 51-74.

https://bitbucket.org/pypa/setuptools/src/8e8c50925f18eafb7e66fe020aa91a85b9a4b122/setuptools/command/install.py?at=default

def run(self):
    # Explicit request for old-style install?  Just do it
    if self.old_and_unmanageable or self.single_version_externally_managed:
        return _install.run(self)

    # Attempt to detect whether we were called from setup() or by another
    # command.  If we were called by setup(), our caller will be the
    # 'run_command' method in 'distutils.dist', and *its* caller will be
    # the 'run_commands' method.  If we were called any other way, our
    # immediate caller *might* be 'run_command', but it won't have been
    # called by 'run_commands'.  This is slightly kludgy, but seems to
    # work.
    #
    caller = sys._getframe(2)
    caller_module = caller.f_globals.get('__name__','')
    caller_name = caller.f_code.co_name

    if caller_module != 'distutils.dist' or caller_name!='run_commands':
        # We weren't called from the command line or setup(), so we
        # should run in backward-compatibility mode to support bdist_*
        # commands.
        _install.run(self)
    else:
        self.do_egg_install()

Я не уверен, предназначено ли такое поведение, но замена

install.run(self)

с участием

install.do_egg_install()

должен решить вашу проблему. По крайней мере, это работает для меня, но я также был бы признателен за более подробный ответ. Спасибо!

person KEgg    schedule 04.03.2014
comment
Это решило мою проблему, спасибо! Но да, я также был бы признателен за подробный ответ о причинах. - person zseder; 06.03.2014
comment
Интересное наблюдение для тех, кому интересно: как объясняется здесь, setuptools подкласс команд distutils.cmd.Command , который представляет собой Python в старом стиле class. По этой причине нельзя использовать super, и необходимо напрямую ссылаться на родительский класс. - person Ioannis Filippidis; 27.03.2015
comment
Разве это не должно быть install.do_egg_install(self)? - person Jonathon Reinhart; 06.08.2016
comment
Я хотел бы проголосовать за этот ответ более одного раза. Кроме того, при использовании python3 вызов super().do_egg_install() работал нормально - person dusktreader; 11.10.2016
comment
@JonathonReinhart, я так думаю. Я вставил код и столкнулся с синтаксической ошибкой, с которой сталкивался задолго до того, как начал изучать Python. - person John Wang; 22.10.2016
comment
@dusktreader ваше исправление python3, похоже, не работает для python 3.6.0. - person Roman; 22.03.2017
comment
Используя это решение, я получил сообщения ERROR: Failed building wheel при установке моего пакета из PyPi. Мой вывод таков, что cmdclass не следует полагаться на производственные модули. Он слишком плохо документирован и имеет слишком много пограничных случаев, когда что-то неожиданно терпит неудачу. - person Quantum7; 27.06.2019
comment
это не работает для python 3.6; он создает папку яйца в папке колеса, в то время как ожидается, что она находится под build/bdist.linux-x86_64/ - person dada; 01.07.2020

Согласно https://stackoverflow.com/a/20196065, более правильным способом сделать это может быть переопределение команды bdist_egg.

Вы можете попробовать:

from setuptools.command.bdist_egg import bdist_egg as _bdist_egg

class bdist_egg(_bdist_egg):
    def run(self):
        call(["pip install -r requirements.txt --no-clean"], shell=True)
        _bdist_egg.run(self)

...

setup(...
    cmdclass={'bdist_egg': bdist_egg},  # override bdist_egg
)

Это сработало для меня, и install_require больше не игнорируется. Тем не менее, я до сих пор не понимаю, почему большинство людей игнорируют cmdclass install и не жалуются на игнорирование install_require.

person Thomas F.    schedule 28.08.2014
comment
Возможно, они используют только pip install и никогда python setup.py install, или при использовании последнего они делают это в virtualenv, уже заполненном зависимостями. - person Ioannis Filippidis; 27.03.2015

Я знаю, что это старый вопрос, но я столкнулся с похожей проблемой. Решение, которое я нашел, устраняет эту проблему для меня очень тонко: класс install, который вы устанавливаете в cmd_class, должен физически называться install. См. этот ответ по связанной проблеме.

Обратите внимание, что я использую имя класса install для своего производного класса, потому что это то, что будет использовать python setup.py --help-commands.

Вы также должны использовать self.execute(_func_name, (), msg="msg") в своем post_install вместо прямого вызова функции.

Таким образом, реализация чего-то подобного должна заставить вас избежать обходного пути do_egg_install, реализованного выше KEgg.

from setuptools.command.install import install as _install
...
def _post_install():
    #code here
class install(_install):
    def run(self):
        _install.run(self)
        self.execute(_post_install, (), msg="message here")
person David Vitale    schedule 25.01.2018
comment
Вы уверены в этом? Я не могу воспроизвести, и это также не решает проблему проверки кадров в реализации setuptools run. - person funky-future; 13.03.2018
comment
Честно говоря, я не уверен. setuptools очень мало документирован и часто ведет себя спорадически. Мне удалось добиться желаемого поведения моего пакета PyPI с помощью воспроизводимых команд после установки, возможно, способами, которые я не понимаю. Вы можете загрузить, разархивировать, а затем изучить файл setup.py самостоятельно: pypi.python.org/pypi/ аркгис - person David Vitale; 14.03.2018