Building and Distributing Packages with Setuptools#
The developer’s guide has been updated. See the most recent version .
TRANSITIONAL NOTE#
Setuptools automatically calls declare_namespace() for you at runtime, but future versions may not. This is because the automatic declaration feature has some negative side effects, such as needing to import all namespace packages during the initialization of the pkg_resources runtime, and also the need for pkg_resources to be explicitly imported before any namespace packages work at all. In some future releases, you’ll be responsible for including your own declaration lines, and the automatic declaration feature will be dropped to get rid of the negative side effects.
During the remainder of the current development cycle, therefore, setuptools will warn you about missing declare_namespace() calls in your __init__.py files, and you should correct these as soon as possible before the compatibility support is removed. Namespace packages without declaration lines will not work correctly once a user has upgraded to a later version, so it’s important that you make this change now in order to avoid having your code break in the field. Our apologies for the inconvenience, and thank you for your patience.
setup.cfg-only projects#
If setup.py is missing from the project directory when a PEP 517 build is invoked, setuptools emulates a dummy setup.py file containing only a setuptools.setup() call.
PEP 517 doesn’t support editable installs so this is currently incompatible with pip install -e . .
This means that you can have a Python project with all build configuration specified in setup.cfg , without a setup.py file, if you can rely on your project always being built by a PEP 517/ PEP 518 compatible frontend.
- Specify build requirements and PEP 517 build backend in pyproject.toml . For example:
[build-system] requires = [ "setuptools >= 40.9.0", ] build-backend = "setuptools.build_meta"
Warning As PEP 517 is new, support is not universal, and frontends that do support it may still have bugs. For compatibility, you may want to put a setup.py file containing only a setuptools.setup() invocation.
Configuration API#
Some automation tools may wish to access data from a configuration file.
Setuptools exposes a read_configuration() function for parsing metadata and options sections into a dictionary.
from setuptools.config import read_configuration conf_dict = read_configuration("/home/user/dev/package/setup.cfg")
By default, read_configuration() will read only the file provided in the first argument. To include values from other configuration files which could be in various places, set the find_others keyword argument to True .
If you have only a configuration file but not the whole package, you can still try to get data out of it with the help of the ignore_option_errors keyword argument. When it is set to True , all options with errors possibly produced by directives, such as attr: and others, will be silently ignored. As a consequence, the resulting dictionary will include no such options.
Forum and Bug Tracker#
Please use GitHub Discussions for questions and discussion about setuptools, and the setuptools bug tracker ONLY for issues you have confirmed via the forum are actual bugs, and which you have reduced to a minimal set of steps to reproduce.
The default behaviour for setuptools will work well for pure Python packages, or packages with simple C extensions (that don’t require any special C header). See Controlling files in the distribution and Data Files Support for more information about complex scenarios, if you want to include other types of files.
Quickstart#
Most of the times, however, you don’t have to… Instead, when creating new Python packages, it is recommended to use a command line tool called build. This tool will automatically download setuptools and any other build-time dependencies that your project might have. You just need to specify them in a pyproject.toml file at the root of your package, as indicated in the following section . You can also install build using pip:
pip install --upgrade build
Important Please note that some operating systems might be equipped with the python3 and pip3 commands instead of python and pip (but they should be equivalent). If you don’t have pip or pip3 available in your system, please check out pip installation docs .
Every python package must provide a pyproject.toml and specify the backend (build system) it wants to use. The distribution can then be generated with whatever tool that provides a build sdist -like functionality.
Basic Use#
When creating a Python package, you must provide a pyproject.toml file containing a build-system section similar to the example below:
[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta"
This section declares what are your build system dependencies, and which library will be used to actually do the packaging.
Note Historically this documentation has unnecessarily listed wheel in the requires list, and many projects still do that. This is not recommended. The backend automatically adds wheel dependency when it is required, and listing it explicitly causes it to be unnecessarily required for source distribution builds. You should only include wheel in requires if you need to explicitly access it during build time (e.g. if your project needs a setup.py script that imports wheel ).
In addition to specifying a build system, you also will need to add some package information such as metadata, contents, dependencies, etc. This can be done in the same pyproject.toml [ 2 ] file, or in a separated one: setup.cfg or setup.py [ 1 ] . The following example demonstrates a minimum configuration (which assumes the project depends on requests and importlib-metadata to be able to run):
[project] name = "mypackage" version = "0.0.1" dependencies = [ "requests", 'importlib-metadata; python_version, ]
[metadata] name = mypackage version = 0.0.1 [options] install_requires = requests importlib-metadata; python_version < "3.8"
from setuptools import setup setup( name='mypackage', version='0.0.1', install_requires=[ 'requests', 'importlib-metadata; python_version == "3.8"', ], )
Finally, you will need to organize your Python code to make it ready for distributing into something that looks like the following (optional files marked with # ):
mypackage ├── pyproject.toml # and/or setup.cfg/setup.py (depending on the configuration method) | # README.rst or README.md (a nice description of your package) | # LICENCE (properly chosen license information, e.g. MIT, BSD-3, GPL-3, MPL-2, etc. ) └── mypackage ├── __init__.py └── . (other Python files)
You now have your distribution ready (e.g. a tar.gz file and a .whl file in the dist directory), which you can upload to PyPI! Of course, before you release your project to PyPI, you’ll want to add a bit more information to help people find or learn about your project. And maybe your project will have grown by then to include a few dependencies, and perhaps some data files and scripts. In the next few sections, we will walk through the additional but essential information you need to specify to properly package your project.
Info: Using setup.py Setuptools offers first class support for setup.py files as a configuration mechanism. It is important to remember, however, that running this file as a script (e.g. python setup.py sdist ) is strongly discouraged, and that the majority of the command line interfaces are (or will be) deprecated (e.g. python setup.py install , python setup.py bdist_wininst , …). We also recommend users to expose as much as possible configuration in a more declarative way via the pyproject.toml or setup.cfg , and keep the setup.py minimal with only the dynamic parts (or even omit it completely if applicable). See Why you shouldn’t invoke setup.py directly for more background.
Overview#
Package discovery#
For projects that follow a simple directory structure, setuptools should be able to automatically detect all packages and namespaces . However, complex projects might include additional folders and supporting files that not necessarily should be distributed (or that can confuse setuptools auto discovery algorithm). Therefore, setuptools provides a convenient way to customize which packages should be distributed and in which directory they should be found, as shown in the example below:
# . [tool.setuptools.packages] find = <> # Scan the project directory with the default parameters # OR [tool.setuptools.packages.find] # All the following settings are optional: where = ["src"] # ["."] by default include = ["mypackage*"] # ["*"] by default exclude = ["mypackage.tests*"] # empty by default namespaces = false # true by default
[options] packages = find: # OR `find_namespace:` if you want to use namespaces [options.packages.find] # (always `find` even if `find_namespace:` was used before) # This section is optional as well as each of the following options: where=src # . by default include=mypackage* # * by default exclude=mypackage.tests* # empty by default
from setuptools import find_packages # or find_namespace_packages setup( # . packages=find_packages( # All keyword arguments below are optional: where='src', # '.' by default include=['mypackage*'], # ['*'] by default exclude=['mypackage.tests'], # empty by default ), # . )
When you pass the above information, alongside other necessary information, setuptools walks through the directory specified in where (defaults to . ) and filters the packages it can find following the include patterns (defaults to * ), then it removes those that match the exclude patterns (defaults to empty) and returns a list of Python packages. For more details and advanced use, go to Package Discovery and Namespace Packages .
Tip Starting with version 61.0.0, setuptools’ automatic discovery capabilities have been improved to detect popular project layouts (such as the flat-layout and src-layout ) without requiring any special configuration. Check out our reference docs for more information, but please keep in mind that this functionality is still considered beta and might change in future releases.
Entry points and automatic script creation#
Setuptools supports automatic creation of scripts upon installation, that run code within your package if you specify them as entry points . An example of how this feature can be used in pip : it allows you to run commands like pip install instead of having to type python -m pip install . The following configuration examples show how to accomplish this:
[project.scripts] cli-name = "mypkg.mymodule:some_func"
[options.entry_points] console_scripts = cli-name = mypkg.mymodule:some_func