Python Packaging - an Overview
Overview: Python packaging#
Python packaging is extremely broad. It has a long history and isn’t easily learned and understood in a day. You need to gradually learn and apply - by packaging yourself. If you have never packaged (or even if you have) you won’t know all the ins and outs - caveats etc.
In this post I am going to attempt to touch on certain parts of packaging gradually until a full overview is formed.
Command Line Scripts#
How do you package command line tools in your package? Command lines tools usually look to the user like a binary and are in /usr/bin
or /usr/local/bin
on unix based machines.
There are 2 mechanisms for this:
scripts
keyword insetup.py
console-scripts
- entrypoints
Scripts#
Write your script in a seperate file:
#!/usr/bin/env python
import funniest
print funniest.joke()
put it in mypackage/bin
In setup.py
setup(
...
scripts=['bin/funniest-joke'],
...
)
When we install the package pip will make it available on the path. This is generalizable - the script could be any type: go
, bash
, etc.
The Console Scripts Entrypoint#
Setuptools allows modules to register entrypoints which other packages can hook into to provide certain functionality
This allows Python functions (not scripts!) to be directly registered as command-line accessible tools.
Create a python module (a new .py
file) and create the function:
import funniest
def main():
print funniest.joke()
in mypackage/package_dir/command_line.py
You can test it directly:
>>> import funniest.command_line
>>> funniest.command_line.main()
register the main function:
setup(
...
entry_points = {
'console_scripts': ['funniest-joke=funniest.command_line:main'],
}
...
)
when the package is installed it will move it to the bin
path
Setuptools will generate a standalone script ‘shim’ which imports your module and calls the registered function.
This method has the advantage that it’s very easily testable, instead of having to shell out to spawn the script we can do:
from unittest import TestCase
from funniest.command_line import main
class TestConsole(TestCase):
def test_basic(self):
main()
To make that more useful we might want to use a context manager for
sys.stdout