Search code examples
pythonpathcommand-line-interfaceexecutionsetup.py

How to use PEP 517 and PEP 518 for locally available (not on PYPI), system-wide CLI Python scripts


I'm expanding my work into CLI apps for some automation and other quality-of-life benefits, and started using setup.cfg and pyproject.toml for these projects instead of the usual requirements.txt . The thing is I would really like these apps not to be shared on PyPi just yet, while I'm playing around with the basic mechanics and use cases, but love how much easier it is to use these facilities to connect python packages to the executable path for my OS. Usually, I would use pipx to help me with this problem and install directly from source, but I'm curious if you have any other strategies.


Solution

  • A summary of entrypoints in pyproject.toml python packaging

    So I finally figured this beast out and know how to initialize entrypoint scripts and make it easy to add to the system's PATH as executables--basically, we can use Python packaging conventions/automation to create system-wide/user-wide accessibility to any command line utility.

    Within the Python packaging API, there are entrypoints we can specify, that were laid down in past PEPs and were instantiated through setup.py . Packaging in Python is going through a slow evolution, which will eventually migrate from setup.py and setup.cfg permanently to pyproject.toml (example of what this new format will look in PEP 621) and the toml language specification, which is being unofficially endorsed by the Python Software Foundation as the preferred file format over yaml (read more about why).

    In the hyperlinked "example" above, we see this: (learn more about the differences) [project.scripts] spam-cli = "spam:main_cli"

    [project.gui-scripts]
    spam-gui = "spam:main_gui"
    
    [project.entry-points."spam.magical"]
    tomatoes = "spam:main_tomatoes"
    

    These replace the following option entry_points within the setup() object:

    # source: https://the-hitchhikers-guide-to-packaging.readthedocs.io/en/latest/creation.html#entry-points
    
    setup(name='zest.releaser',
          ...
          entry_points={
              'console_scripts':
                  ['release = zest.releaser.release:main',
                   'prerelease = zest.releaser.prerelease:main',
                   ]}
          )
    

    The exciting part of this is we can likely reduce our entire packaging concerns (eventually) to just the pyproject.toml file (we're not there, yet, see this information about using setup.py as a shim so we can still benefit from the pip install --editable . command, which has the added benefit of better read-ability, locking down build environment and package environment dependencies, and creating a universal standard API interface through which various third party dependency tools like Poetry (extra source) and build tools like Black (and it's CLI config options) can centralize their configuration to make a more pythonic, deterministic way to create effective building and packaging of new and existing software.