Skip to content

Recipe Setuptools Entry Point

Geoffrey Spear edited this page Jan 14, 2019 · 14 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 as 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, **kwargs):
    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 []

    kwargs.setdefault('hiddenimports', [])
    packages = []
    for distribution in kwargs['hiddenimports']:
        packages += get_toplevel(distribution)

    kwargs.setdefault('pathex', [])
    # 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
    kwargs['pathex'] = [ep.dist.location] + kwargs['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:
        print("import", ep.module_name, file=fh)
        print("%s.%s()" % (ep.module_name, '.'.join(ep.attrs)), file=fh)
        for package in packages:
            print("import", package, file=fh)

    return Analysis(
        [script_path] + kwargs.get('scripts', []),
        **kwargs
    )

Note

This recipe contains possible workaround to #1086 & #305