Execute a Python script post install using distutils / setuptools
Asked Answered
Y

2

36

Note: distutils is deprecated and the accepted answer has been updated to use setuptools

I'm trying to add a post-install task to Python distutils as described in How to extend distutils with a simple post install script?. The task is supposed to execute a Python script in the installed lib directory. This script generates additional Python modules the installed package requires.

My first attempt is as follows:

from distutils.core import setup
from distutils.command.install import install

class post_install(install):
    def run(self):
        install.run(self)
        from subprocess import call
        call(['python', 'scriptname.py'],
             cwd=self.install_lib + 'packagename')

setup(
 ...
 cmdclass={'install': post_install},
)

This approach works, but as far as I can tell has two deficiencies:

  1. If the user has used a Python interpreter other than the one picked up from PATH, the post install script will be executed with a different interpreter which might cause a problem.
  2. It's not safe against dry-run etc. which I might be able to remedy by wrapping it in a function and calling it with distutils.cmd.Command.execute.

How could I improve my solution? Is there a recommended way / best practice for doing this? I'd like to avoid pulling in another dependency if possible.

Yearly answered 23/7, 2013 at 9:33 Comment(1)
For those who want to be able to use also python setup.py install, as well as pip install, see: stackoverflow.com/questions/21915469/…Abhorrent
Y
39

The way to address these deficiences is:

  1. Get the full path to the Python interpreter executing setup.py from sys.executable.

  2. Classes inheriting from setuptools.Command (such as setuptools.command.install.install which we use here) implement the execute method, which executes a given function in a "safe way" i.e. respecting the dry-run flag.

    Note however that the --dry-run option is currently broken and does not work as intended anyway.

I ended up with the following solution:

import os, sys
from setuptools import setup
from setuptools.command.install import install as _install


def _post_install(dir):
    from subprocess import call
    call([sys.executable, 'scriptname.py'],
         cwd=os.path.join(dir, 'packagename'))


class install(_install):
    def run(self):
        _install.run(self)
        self.execute(_post_install, (self.install_lib,),
                     msg="Running post install task")


setup(
    ...
    cmdclass={'install': install},
)

Note that I use the class name install for my derived class because that is what python setup.py --help-commands will use.

Yearly answered 10/8, 2013 at 8:7 Comment(7)
thanks, this really helped out, I also needed to follow (stackoverflow.com/questions/15853058/…) to avoid an error in my pip install. I put it all together in a blog post (diffbrent.ghost.io/…). Let me know if I missed something.Heed
@Heed Glad to hear it helped! Note my comment on why I used install as class name.Yearly
it works, but I wasn't been able to have the custom install executed with pip install -e name. ps, just found this link, see the BONUS section.Classroom
Looks very good, the post install part still runs fine with setuptools instead of distutils, but the ability of setuptools to handle dependencies seems to be lost (they are just ignored).Alkmaar
@Classroom 's link has moved to: blog.niteo.co/setuptools-run-custom-code-in-setup-pyLaodicea
Since distutils is deprecated, how would this solution change for setuptools?Tweed
setuptools is a compatible replacement for distutils. I've updated my answer.Yearly
A
-1

I think the easiest way to perform the post-install, and keep the requirements, is to decorate the call to setup(...):

from setup tools import setup


def _post_install(setup):
    def _post_actions():
        do_things()
    _post_actions()
    return setup

setup = _post_install(
    setup(
        name='NAME',
        install_requires=['...
    )
)

This will run setup() when declaring setup. Once done with the requirements installation, it will run the _post_install() function, which will run the inner function _post_actions().

Adaadabel answered 7/6, 2018 at 12:44 Comment(1)
this code seems don't work as desired... it will directly run the _post_action ...Prerequisite

© 2022 - 2024 — McMap. All rights reserved.