Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get entry point script file location in setuputils package?

So I have an entry point defined in my setup.py [console_scripts] section. The command is properly installed and works fine, but I need a way to programatically find out the path to the script (e.g. on windows it'll be something like C:/my/virtual/env/scripts/my_console_script.exe). I need this so I can pass that script path as an argument to other commands, regardless of where the package is installed. Setuputils provides the pkg_resources, but that doesn't seem to expose any way of actually getting at the raw installed paths, only loadable objects.

Edit: To make the use case plain here's the setup.

I have a plugin-driven application that communicates with various local services. One of these plug-ins ties into the alerting interface of an NMS package. The only way this alerting package can get alerts out to an arbitrary handler is to call a script - the path to execute (the console_scripts entry point in this case) is register as a complete path - that's the path I need to get.

like image 263
Tyler Eaves Avatar asked Jul 31 '14 18:07

Tyler Eaves


People also ask

What is entry point in setup py?

Entry points are a type of metadata that can be exposed by packages on installation. They are a very useful feature of the Python ecosystem, and come specially handy in two scenarios: 1. The package would like to provide commands to be run at the terminal. This functionality is known as console scripts.

What is an entry point script?

In NetSuite terminology, an "Entry Point Script" is the module you write that will be assigned as the source file on a Script record. If your module requires an @NScriptType tag, then it is most certainly an Entry Point Script.

What is Load_entry_point?

exit( load_entry_point('rss2sms==0.0.1', 'console_scripts', 'rss2sms')() ) This executable is just a simple python module which, when we call it, uses the pkg_resources library to look up what python module our setup.py says we should call.

What is console_scripts?

The console_scripts Entry Point Setuptools allows modules to register entrypoints which other packages can hook into to provide certain functionality. It also provides a few itself, including the console_scripts entry point.


1 Answers

Well, you could pass an option of the form --install-option="--install-scripts=/usr/local/bin" to pip and just set the path yourself. But I understand why you might not want to hardcode that in a cross-platform project.

So, we just need to find out what directory setuptools is actually using. Unfortunately, the path is determined in the middle of a whole bunch of actual setup code, rather than in a separate function we can just call.

So the next step is to write a custom install command that observes and saves the path. (For that matter, you could also set self.install_scripts to a directory of your choice in this custom installer class, thereby keeping that piece of config in one place (the package) rather than in the package and in a command line arg to setup...)

Example:

from setuptools import setup
from setuptools.command.install import install
from distutils.command.build_py import build_py
import os

class InstallReadsScriptDir(install):
    def run(self):
        self.distribution._x_script_dir = self.install_scripts
        install.run(self)

class BuildConfiguresScriptDir(build_py):
    def run(self):
        build_py.run(self)
        if self.dry_run:
            return
        script_dir = self.distribution._x_script_dir  # todo: check exists
        target = os.path.join(self.build_lib, 'mypackage', 'scriptdir.py')
        with open(target, 'w') as outfile:
            outfile.write('path = """{}"""'.format(script_dir))

setup(
    name="MyPackage",
    version='0.0.1',
    packages = ['mypackage'],
    entry_points = {
        'console_scripts': [
            'footest = mypackage:main',
        ],
    },
    cmdclass= {
        'install': InstallReadsScriptDir,
        'build_py': BuildConfiguresScriptDir,
    },
)

Possible objections:

  • Some people don't like any form of code generation. In this case it's more like configuration, though putting it in a .py in the package makes it easy to get at from Python code anywhere.

  • On the surface it looks like a bit of a hack. However, setuptools is specifically designed to allow custom command classes. Nothing here is really accessing anything private, except maybe the value passing using the distribution object which is just to avoid a global.

  • Only gets the directory, not the executable names. You should know the names though, based on your entry point configuration. Getting the names would require calling the parsing methods for your entry point spec and generating more code.

  • It's going to need some bugfixing if you e.g. build without installing. sdist is safe though.

  • develop is possible but needs another command class. install isn't called and build_py is only called if you set use_2to3. A custom develop command can get the path as in the install example, but as self.script_dir. From there you have to decide if you're comfortable writing a file to your source directory, which is in self.egg_path -- as it will get picked up by install later, though it should be the same file (and the build command will overwrite the file anyway)

Addendum

This little flash of insight may be more elegant as it does not require saving the path anywhere, but still gets the actual path setuptools uses, though of it assumes no configuration has changed since the install.

from setuptools import Distribution
from setuptools.command.install import install

class OnlyGetScriptPath(install):
    def run(self):
        # does not call install.run() by design
        self.distribution.install_scripts = self.install_scripts

def get_setuptools_script_dir():
    dist = Distribution({'cmdclass': {'install': OnlyGetScriptPath}})
    dist.dry_run = True  # not sure if necessary, but to be safe
    dist.parse_config_files()
    command = dist.get_command_obj('install')
    command.ensure_finalized()
    command.run()
    return dist.install_scripts

Addendum 2

Just realized/remembered that pip creates an installed-files.txt in the egg-info, which is a list of paths relative to the package root of all the files including the scripts. pkg_resources.get_distribution('mypackage').get_metadata('installed-files.txt') will get this. It's only a pip thing though, not created by setup.py install. You'd need to go through the lines looking for your script name and then get the absolute path based on your package's directory.

like image 83
Jason S Avatar answered Sep 27 '22 00:09

Jason S