$ ls
links  published  categories  alts  content

$ cat links
- Home
- Author

$ cat published


$ cat categories
- Python
- Reference

$ cat alts
- Gopher Mirror
- text/plain; width=40
- text/plain; width=72
- text/plain; width=120
- application/x-troff

$ cat content

                    The setup.py Guide I Wish I had
________________________________________________________________________

I'm a big proponent of using all the built-in tools in Python.  This
manifests in weird ways: I like the http.server module; I abuse multi-
processing.Manager; and I like using pure distutils.

The latter gives me problems sometimes because no one seems to use it
like I do.  I often find myself getting lost in Python's lackluster doc-
umentation for this module trying to just find the name of the argument
I need.  It's starting to get a little silly how often and how long I
spend on this problem.

Of course, one solution to this problem is "just use an IDE with auto-
completion."  Another is "just use setuptools which has better documen-
tation."  For various reasons, I prefer to use simpler editors and also
to use standard library functions.

To that end, this document is a consolidation of the things I'm usually
looking for.  It mostly serves as a reference for myself, but maybe it
will be helpful to someone else.

Before the document gets into the details, here are some links that are
helpful.

API documentation for distutils.core.setup
[0]: https://docs.python.org/3/distutils/apiref.html

Official examples for distutils.core.setup
[1]: https://docs.python.org/3/distutils/setupscript.html

List of valid classifier values
[2]: https://pypi.org/classifiers/

API documentation for pkgutil.get_data
[3]: https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data


                          The Simplest Script
________________________________________________________________________

If you are creating a package called MYPACKAGE, your script would look
like this.

--------8<--------------------------------------------------------------
from distutils.core import setup

setup(
        name='MYPACKAGE',
        version='0.1.0',
        packages=[
                'MYPACKAGE',
        ],
)
-------->8--------------------------------------------------------------

You should have a directory called MYPACKAGE with at least an
__init__.py file inside.

--------8<--------------------------------------------------------------
/
  /setup.py
  /MYPACKAGE/
    /MYPACKAGE/__init__.py
-------->8--------------------------------------------------------------


                          Adding Dependencies
________________________________________________________________________

If you need certain dependencies to be installed, you can specify them
with the requires keyword.  In this example, we require at least version
2.20.0 of requests.

--------8<--------------------------------------------------------------
from distutils.core import setup

setup(
        name='MYPACKAGE',
        version='0.1.0',
        packages=[
                'MYPACKAGE',
        ],
        requires=[
                'requests>=2.20.0',
        ],
)
-------->8--------------------------------------------------------------


                          Adding an Executable
________________________________________________________________________

Edit: Oops.  This one does actually depend on setuptools.  If you in-
stall the package with "pip install ."  then it will automatically use
setuptools for you, hence my confusion.

Sometimes you aren't trying to just create a library but also need an
actual executable.  Somewhat confusingly, in distutils land, this is
called an "entry point."  If you want an executable called MYEXEC for
the package MYPACKAGE, then you'll want this.

--------8<--------------------------------------------------------------
# File: setup.py

from distutils.core import setup

setup(
        name='MYPACKAGE',
        version='0.1.0',
        packages=[
                'MYPACKAGE',
        ],
        entry_points={
                'console_scripts': [
                        'MYEXEC=MYPACKAGE.__main__:cli',
                ],
        },
)
-------->8--------------------------------------------------------------

You should have a __main__.py script with a cli function inside.  A sim-
ple __main__.py script looks like:

--------8<--------------------------------------------------------------
# File: __main__.py

from . import hello

def main(name):
        print(f'The output of hello({name!r}) is {hello(name)!r}')

def cli():
        import argparse

        parser = argparse.ArgumentParser()
        parser.add_argument('name')
        args = vars(parser.parse_args())

        main(**args)

if __name__ == '__main__':
        cli()
-------->8--------------------------------------------------------------

The corresponding __init__.py might look like:

--------8<--------------------------------------------------------------
# File: __init__.py

def hello(name):
        return name.upper()
-------->8--------------------------------------------------------------

Now after you install the package, you can use MYEXEC as a normal script
and pass it arguments like: MYEXEC George.

The directory structure here is:

--------8<--------------------------------------------------------------
/
  /setup.py
  /MYPACKAGE/
    /MYPACKAGE/__init__.py
    /MYPACKAGE/__main__.py
-------->8--------------------------------------------------------------


                     Changing the Package Directory
________________________________________________________________________

Sometimes you want to put your code in a different directory than Python
expects.  By default, Python wants MYPACKAGE to be located at ./MYPACK-
AGE, but you can change this to look at ./src/MYPACKAGE or any other di-
rectory instead.

--------8<--------------------------------------------------------------
# File: setup.py

from distutils.core import setup

setup(
        name='MYPACKAGE',
        version='0.1.0',
        packages=[
                'MYPACKAGE',
        ],
        package_dir={
                'MYPACKAGE': 'src/MYPACKAGE',
        },
)
-------->8--------------------------------------------------------------

The directory structure now looks like:

--------8<--------------------------------------------------------------
/
  /setup.py
  /src/
    /src/MYPACKAGE/
      /src/MYPACKAGE/__init__.py
-------->8--------------------------------------------------------------


                       Including Extra Data Files
________________________________________________________________________

You may want to include some extra files with your package.  For in-
stance, I like to include an index.html page with my web server pack-
ages.

--------8<--------------------------------------------------------------
# File: setup.py

from distutils.core import setup

setup(
        name='MYPACKAGE',
        version='0.1.0',
        packages=[
                'MYPACKAGE',
        ],
        package_data={
                'MYPACKAGE': [
                        'static/*',
                ],
        },
)
-------->8--------------------------------------------------------------

Now any file in the static directory in your package will be included.

--------8<--------------------------------------------------------------
/
  /setup.py
  /MYPACKAGE/
    /MYPACKAGE/__init__.py
    /MYPACKAGE/static/
      /MYPACKAGE/static/index.html
-------->8--------------------------------------------------------------

To retrieve this file at runtime, you can use pkgutil.get_data

--------8<--------------------------------------------------------------
# File: __init__.py

import pkgutil

index_html = pkgutil.get_data('MYPACKAGE', 'static/index.html')

print(type(index_html))
#  => bytes
-------->8--------------------------------------------------------------


                         A More Complete Script
________________________________________________________________________

There's lots of parameters, but I suspect that the ones that are most
useful for actually distributing a package are as follows.

--------8<--------------------------------------------------------------
# File: setup.py

from distutils.core import setup

long_description = """\
This is the long description for my package.

It can be pretty long.

You can either use reStructuredText or GitHub Flavored Markdown.
"""

setup(
        name='MYPACKAGE',
        version='0.1.0',
        description="One line description",
        long_description=long_description,
        author='John Smith',
        author_email='johnsmith@example.com',
        url='https://github.com/example/MYPACKAGE',
        license='MIT',
        keywords=[
                'cool',
                'useful',
                'whatever',
        ],
        classifiers=[
                'Development Status :: 1 - Planning',
                'Programming Language :: Python :: 3.8',
        ],
        packages=[
                'MYPACKAGE',
        ],
        requires=[
                'requests>=2.20.0',
        ],
)
-------->8--------------------------------------------------------------