Skip to content
Search

Setuptools

lastmod 2026-02-19created 2014-04-2963 edits

Setuptools is a collection of tools for building and distributing Python packages. It simplifies package distribution and management.

Categories

Configuration

extras_require

Sets extra dependencies.

extras_require={
    'test': tests_require,
    },

With this set, you can install the packages needed for testing in advance with a command like this:

pip install -e .[test]

A typical example is installing with pip install aiohttp[speedups] in projects like aiohttp; this corresponds to extras_require.

long_description_content_type

Set the value of long_description to the contents (not the path) of the README file itself. Set the long_description_content_type to an accepted Content-Type-style value for your README file’s markup, such as text/plain, text/x-rst (for reStructuredText), or text/markdown.

extra_compile_args

from distutils.core import setup
from distutils.extension import Extension

setup(
    name="myPy",
    ext_modules = [Extension(
        "MyPyExt",
        ["MyPyExt.cpp"],
        libraries = ["boost_python"],
        extra_compile_args = ["-g0"]
        )]
    )

setup.py

Minimal version

from setuptools import setup, find_packages

setup(
    name='MyPackageName',
    version='1.0.0',
    url='https://github.com/mypackage.git',
    author='Author Name',
    author_email='[email protected]',
    description='Description of my package',
    packages=find_packages(),    
    install_requires=['numpy >= 1.11.1', 'matplotlib >= 1.5.1'],
)

recc No-cython version

# -*- coding: utf-8 -*-

import os
from typing import List
from setuptools import setup, find_packages

SOURCE_PATH = os.path.abspath(__file__)
SOURCE_DIR = os.path.dirname(SOURCE_PATH)
RECC_DIR = os.path.join(SOURCE_DIR, "recc")
README_PATH = os.path.join(SOURCE_DIR, "README.md")
REQUIREMENTS_MAIN = os.path.join(SOURCE_DIR, "requirements.main.txt")
REQUIREMENTS_DOCS = os.path.join(SOURCE_DIR, "requirements.docs.txt")
REQUIREMENTS_TEST = os.path.join(SOURCE_DIR, "requirements.test.txt")
REQUIREMENTS_SETUP = os.path.join(SOURCE_DIR, "requirements.setup.txt")

# Make sure you're in the proper directory, whether you're using pip.
os.chdir(SOURCE_DIR)

from recc.util.version import normalize_version, version_text  # noqa


def read_file(path, encoding="utf-8") -> str:
    with open(path, encoding=encoding) as f:
        return f.read()


def read_packages(path, encoding="utf-8") -> List[str]:
    content = read_file(path, encoding)
    lines = content.split("\n")
    lines = map(lambda x: x.strip(), lines)
    lines = filter(lambda x: x and x[0] != "#", lines)
    return list(lines)


def setup_main():
    long_description = read_file(README_PATH)
    install_requires = read_packages(REQUIREMENTS_MAIN)
    tests_require = read_packages(REQUIREMENTS_TEST)
    setup_requires = read_packages(REQUIREMENTS_SETUP)
    version = normalize_version(version_text)
    packages = find_packages(where=SOURCE_DIR, exclude=("tester*",))

    setup_config = {
        "name": "recc",
        "version": version,
        "description": "The Answer, No-Code Development Platform.",
        "long_description": long_description,
        "long_description_content_type": "text/markdown",
        "license": "MIT License",
        "keywords": [
            "Answer",
            "No-code",
            "Visual Graph",
            "Visual Programming",
            "Machine Learning",
            "Deep Learning",
        ],
        "url": "https://answerdoc.bogonets.com",
        "project_urls": {
            "Documentation": "https://answerdoc.bogonets.com",
            "Source": "https://github.com/bogonets/answer",
        },
        "packages": packages,
        "package_dir": {"recc": "recc"},
        "include_package_data": True,
        "install_requires": install_requires,
        "setup_requires": setup_requires,
        "python_requires": ">=3.8",
        "zip_safe": False,
        "test_suite": "test",
        "tests_require": tests_require,
        "classifiers": [
            "Development Status :: 4 - Beta",
            "Environment :: Web Environment",
            "Environment :: GPU :: NVIDIA CUDA",
            "Framework :: AsyncIO",
            "Intended Audience :: Developers",
            "Intended Audience :: System Administrators",
            "Operating System :: OS Independent",
            "Programming Language :: Python",
            "Programming Language :: Python :: 3.8",
            "Programming Language :: JavaScript",
            "Topic :: Scientific/Engineering :: Image Processing",
            "Topic :: Scientific/Engineering :: Image Recognition",
            "Topic :: Scientific/Engineering :: Information Analysis",
            "Topic :: Scientific/Engineering :: Visualization",
            "Topic :: Software Development",
            "Topic :: Utilities",
            "Topic :: Internet :: WWW/HTTP",
            "License :: OSI Approved :: MIT License",
        ],
        "entry_points": {
            "console_scripts": [
                "recc = recc.app.entrypoint:main",
            ]
        },
        "author": "yourname",
        "author_email": "[email protected]",
        "maintainer": "yourname",
        "maintainer_email": "[email protected]",
    }

    setup(**setup_config)


if __name__ == "__main__":
    setup_main()

Editable

If you add the -e option as follows,

pip install -e .

the files are not all installed into site-packages; instead, it links to the directory holding the original source. Since you can edit it right away (editable), it can be used like a developer mode.

Data file support

About how to include regular data files in a Python package. In summary:

include_package_data
If True, includes files as defined in MANIFEST.in.
package_data
Lets you specify additional patterns.
exclude_package_data
Specifies patterns for data files and directories that should not be included when the package is installed.

MANIFEST

Related official documentation
4. Creating a Source Distribution — Python 3.10.0a3 documentation
4. Creating a Source Distribution — Python documentation, annotated edition
4. Creating a Source Distribution — Python 3.10.6 documentation
[Recommended] 8. Command Reference — Python 3.10.0b3 documentation - Location of the official documentation on manifest template commands.

The manifest template commands are:

Command

Description

include pat1 pat2 ...

include all files matching any of the listed patterns

exclude pat1 pat2 ...

exclude all files matching any of the listed patterns

recursive-include dir pat1 pat2 ...

include all files under dir matching any of the listed patterns

recursive-exclude dir pat1 pat2 ...

exclude all files under dir matching any of the listed patterns

global-include pat1 pat2 ...

include all files anywhere in the source tree matching any of the listed patterns

global-exclude pat1 pat2 ...

exclude all files anywhere in the source tree matching any of the listed patterns

prune dir

exclude all files under dir

graft dir

include all files under dir

The patterns here are Unix-style glob patterns:

  • * matches any sequence of regular filename characters,
  • ? matches any single regular filename character,
  • [range] matches any of the characters in range (e.g., a-z, a-zA-Z, a-f0-9_.).
  • The definition of “regular filename character” is platform-specific:
    • On Unix it is anything except slash.
    • On Windows it is anything except backslash or colon.

Sometimes this is enough, but usually you will want to specify additional files to distribute. The typical way to do this is to write a manifest template, called MANIFEST.in by default. The manifest template is just a list of instructions for how to generate your manifest file, MANIFEST, which is the exact list of files to include in your source distribution. The sdist command processes this template and generates a manifest based on its instructions and what it finds in the file system.

If you prefer to roll your own manifest file, the format is simple: one filename per line, regular files (or symlinks to them) only. If you do supply your own MANIFEST, you must specify everything: the default set of files described above does not apply in this case.

Example

include doc/*
include requirements.*.txt
include README.md

include pyproject.toml

# Include the README
include *.md

# Include the license file
include LICENSE.txt

# Include setup.py
include setup.py

# Include the data files
recursive-include data *

Pre-release versioning

Regardless of the base versioning scheme, pre-releases for a given final release may be published as:

  • zero or more dev releases (denoted with a .devN suffix)
  • zero or more alpha releases (denoted with a .aN suffix)
  • zero or more beta releases (denoted with a .bN suffix)
  • zero or more release candidates (denoted with a .rcN suffix)

pip and other modern Python package installers ignore pre-releases by default when deciding which versions of dependencies to install.

setup.cfg

[metadata]
name = type-serialize
description = Serialize with type annotations
version = attr: type_serialize.__version__
url = https://github.com/yourid/type-serialize
author = yourname
author_email = [email protected]
maintainer = yourname
maintainer_email = [email protected]
project_urls =
    GitHub: issues = https://github.com/yourid/type-serialize/issues
    GitHub: repo = https://github.com/yourid/type-serialize
long_description = file: README.md
long_description_content_type = text/markdown
license = MIT License
license_files = LICENSE
keywords =
    serialize
classifiers =
    Development Status :: 4 - Beta
    Intended Audience :: Developers
    Operating System :: POSIX
    Operating System :: MacOS :: MacOS X
    Operating System :: Microsoft :: Windows
    Programming Language :: Python
    Programming Language :: Python :: 3
    Programming Language :: Python :: 3.8
    Programming Language :: Python :: 3.9
    Programming Language :: Python :: 3.10
    Topic :: Software Development
    Topic :: Utilities
    License :: OSI Approved :: MIT License

[options]
python_requires = >=3.8
packages = find:
tests_require =
    pytest

[options.extras_require]
full =
    numpy
    orjson
    msgpack

[options.packages.find]
exclude =
    test*

[options.entry_points]
console_scripts =
    type_serialize = type_serialize.app.cli:main

Specifying values

  • options.extras_require - Optional dependencies.
    • Appending [all], as in pip install fastapi[all], installs the optional dependency packages.
    • An example:

[metadata] name = Package-A

[options.extras_require] PDF =

ReportLab>=1.2
RXP

</syntaxhighlight>

  • classifiers: - See PyPI:Classifiers.
  • attr: - Value is read from a module attribute. attr: supports callables and iterables; unsupported types are cast using str().
    • In order to support the common case of a literal value assigned to a variable in a module containing (directly or indirectly) third-party imports, attr: first tries to read the value from the module by examining the module's AST. If that fails, attr: falls back to importing the module.
  • file: - Value is read from a list of files and then concatenated

INFORMATION

The file: directive is sandboxed and won't reach anything outside the project directory (i.e. the directory containing setup.cfg/pyproject.toml).

install_requires file error

There is a file: syntax like the following:

install_requires = file: requirements.txt

The file: directive for reading requirements is supported since version 62.6. The file format is similar to the requirements.txt file, but every non-comment line must conform to PEP 508 (pip-specific syntaxes, e.g. -c/-r/-e flags, are not supported). Library developers should avoid tightly pinning their dependencies to a specific version (e.g. "Locked" requirements file)

If it doesn't work properly, write it directly in setup.py:

# -*- coding: utf-8 -*-

import os
from setuptools import setup
from typing import List

SOURCE_PATH = os.path.abspath(__file__)
SOURCE_DIR = os.path.dirname(SOURCE_PATH)
REQUIREMENTS_MAIN = os.path.join(SOURCE_DIR, "requirements.main.txt")


def install_requires(encoding="utf-8") -> List[str]:
    with open(REQUIREMENTS_MAIN, encoding=encoding) as f:
        content = f.read()
    lines = content.split("\n")
    lines = map(lambda x: x.strip(), lines)
    lines = filter(lambda x: x and x[0] != "#", lines)
    return list(lines)


if __name__ == "__main__":
    setup(install_requires=install_requires())

Or use distutils.text_file.TextFile:

install_requires = distutils.text_file.TextFile(filename="./requirements.txt").readlines()

recc eample

Uses Cython with the -g0 option to generate shared libraries and build a wheel.

# -*- coding: utf-8 -*-

import os
import re
import sys
import codecs

from setuptools import setup, find_packages
from setuptools.command.build_py import build_py
from Cython.Build import cythonize


SOURCE_PATH = os.path.abspath(__file__)
SOURCE_DIR = os.path.dirname(SOURCE_PATH)
RECC_DIR = os.path.join(SOURCE_DIR, "recc")
README_PATH = os.path.join(SOURCE_DIR, "README.md")
REQUIREMENTS_MAIN = os.path.join(SOURCE_DIR, "requirements.main.txt")
REQUIREMENTS_DOCS = os.path.join(SOURCE_DIR, "requirements.docs.txt")
REQUIREMENTS_TEST = os.path.join(SOURCE_DIR, "requirements.test.txt")
REQUIREMENTS_SETUP = os.path.join(SOURCE_DIR, "requirements.setup.txt")

SOURCE_FILTERS = (
    re.compile(r".*__init__\.py$"),
    re.compile(r".*__main__\.py$"),
)

# Ensure we're in the proper directory whether or not we're being used by pip.
os.chdir(SOURCE_DIR)

from recc.util.version import normalize_version, version_text  # noqa


def ignore_filter(sources, filters=SOURCE_FILTERS):
    result = sources
    for f in filters:
        result = list(filter(lambda x: f.match(x) is None, result))
    return result


def is_match_filter(source, filters=SOURCE_FILTERS):
    for f in filters:
        if f.match(source) is not None:
            return True
    return False


def get_children(path):
    result = []
    for parent, _, files in os.walk(path):
        for name in files:
            result.append(os.path.join(parent, name))
    return result


def get_children_with_match(path, regexp=".*"):
    result = []
    for cursor in get_children(path):
        if re.match(regexp, cursor) is not None:
            result.append(cursor)
    return result


def read_file(path, encoding="utf-8"):
    with codecs.open(filename=path, encoding=encoding) as f:
        return f.read()


def find_fist_positional_argument(args):
    # args[0] is script
    for a in args[1:]:
        if a[0] != "-":
            return a


def is_bdist_wheel():
    return find_fist_positional_argument(sys.argv) == "bdist_wheel"


class NoPythonBuildPy(build_py):
    def find_package_modules(self, package, package_dir):
        # ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
        modules = super().find_package_modules(package, package_dir)
        filtered_modules = []
        for pkg, mod, filepath in modules:
            if is_match_filter(filepath):
                filtered_modules.append((pkg, mod, filepath))
        return filtered_modules


def setup_main():
    long_description = read_file(README_PATH)
    install_requires = read_file(REQUIREMENTS_MAIN).split("\n")
    tests_require = read_file(REQUIREMENTS_TEST).split("\n")
    setup_requires = read_file(REQUIREMENTS_SETUP).split("\n")

    files = get_children_with_match(path=RECC_DIR, regexp=r".*\.py$")
    files = ignore_filter(files)

    compiler_directives = {"language_level": 3}
    cython_modules = cythonize(
        module_list=files, compiler_directives=compiler_directives,
    )
    for ext in cython_modules:
        ext.extra_compile_args = ["-g0"]

    setup_config = {
        "name": "recc",
        "version": normalize_version(version_text),
        "description": "A Python library for the Docker Engine API.",
        "long_description": long_description,
        "long_description_content_type": "text/markdown",
        "url": "https://answerdoc.bogonets.com",
        "project_urls": {
            "Documentation": "https://answerdoc.bogonets.com",
            "Source": "https://github.com/bogonets",
        },
        "packages": find_packages(where=SOURCE_DIR, exclude=("test",)),
        "package_dir": {"recc": "recc"},
        "package_data": {"recc": ["node/*.json"]},
        "install_requires": install_requires,
        "setup_requires": setup_requires,
        "python_requires": ">=3.7",
        "zip_safe": False,
        "test_suite": "test",
        "tests_require": tests_require,
        "classifiers": [
            "Development Status :: 4 - Beta",
            "Environment :: Web Environment",
            "Intended Audience :: Developers",
            "Intended Audience :: System Administrators",
            "Operating System :: OS Independent",
            "Programming Language :: Python",
            "Programming Language :: Python :: 3.7",
            "Topic :: Software Development",
            "Topic :: Utilities",
            "Topic :: Internet :: WWW/HTTP",
        ],
        "entry_points": {
            "console_scripts": [
                "recc = recc.app.entrypoint:main",
            ]
        },
        "maintainer": "yourname",
        "maintainer_email": "[email protected]",
    }

    if is_bdist_wheel():
        setup_config["ext_modules"] = cython_modules
        setup_config["cmdclass"] = {"build_py": NoPythonBuildPy}

    setup(**setup_config)


if __name__ == "__main__":
    setup_main()

How to make a Python package

How to turn a simple Python utility into a package for sharing:

  1. Write the functionality
  2. Create the basic package structure with flit, register it on TestPyPI, and test
  3. Actually publish to PyPI with flit
  4. Add a README and CHANGELOG
  5. Apply formatting (black), test coverage (coverage), code quality (flake8, pylint, mccabe), static analysis (mypy), etc. with tox
  6. Set up automatic builds with GitHub Actions, and on every commit test coverage with Codecov & code quality with Clode Climate
  7. Use make to quickly run only pylint, coverage, etc.
  8. Set up automatic publishing with flit in GitHub Actions

How to upload using twine

twine upload dist/*

Troubleshooting

ModuleNotFoundError: No module named 'pkg resources'

...
ModuleNotFoundError: No module named 'pkg resources'

Install the package.

pip install setuptools

See also

Favorite site

Guide

Samples

author Zer0 sitemap