Recipe Setuptools Entry Point

Arturo Filastò edited this page Jan 27, 2017 · 8 revisions

Packaging from setuptools entry point

setuptools/pkg_resources have a feature called Automatic Script Creation. If your Python package is using this feature to create console or gui scripts, you will have a hard time packaging this with PyInstaller. The reason for this is that the generated script basically looks like this one:

from pkg_resources import load_entry_point
load_entry_point('myCoolApp==0.4.3', 'console_scripts', 'myEntryPoint')()

So it does not import your module, but out-tasks this to load_entry_point. PyInstaller can not detect this when passed the script.

The Recipe

Now instead of running Analysis() on your script, simply run Entrypoint() (see below) on the entry point definition:

a = Entrypoint('myCoolApp', 'console_scripts', 'myEntryPoint')

Entrypoint() automatically creates and analyzes a script which basically does the same has the one shown on the top of this page – but using a syntax PyInstaller can analyze.

For this to work, place the following snippet into your .spec file:

def Entrypoint(dist, group, name,
               scripts=None, pathex=None, hiddenimports=None,
               hookspath=None, excludes=None, runtime_hooks=None):
    import pkg_resources

    # get toplevel packages of distribution from metadata
    def get_toplevel(dist):
        distribution = pkg_resources.get_distribution(dist)
        if distribution.has_metadata('top_level.txt'):
            return list(distribution.get_metadata('top_level.txt').split())
        else:
            return []

    hiddenimports = hiddenimports or []
    packages = []
    for distribution in hiddenimports:
        packages += get_toplevel(distribution)

    scripts = scripts or []
    pathex = pathex or []
    # get the entry point
    ep = pkg_resources.get_entry_info(dist, group, name)
    # insert path of the egg at the verify front of the search path
    pathex = [ep.dist.location] + pathex
    # script name must not be a valid module name to avoid name clashes on import
    script_path = os.path.join(workpath, name + '-script.py')
    print "creating script for entry point", dist, group, name
    with open(script_path, 'w') as fh:
        fh.write("import {0}\n".format(ep.module_name))
        fh.write("{0}.{1}()\n".format(ep.module_name, '.'.join(ep.attrs)))
        for package in packages:
            fh.write("import {0}\n".format(package))

    return Analysis([script_path] + scripts, pathex, hiddenimports, hookspath, excludes, runtime_hooks)

Note

This recipe contains possible workaround to #1086 & #305