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
- Packaging and distributing projects — Python Packaging User Guide
- Configuring setup() using setup.cfg files
- [Recommended] Starting a Python project - Nose — flowdas
extras_require
Sets extra dependencies.
With this set, you can install the packages needed for testing in advance with a command like this:
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
- Stackoverflow - Distutils - How to disable including debug symbols when building an extension
- Stackoverflow - How may I override the compiler (gcc) flags that setup.py uses by default?
- Stackoverflow - extra_compile_args in Cython
- Stackoverflow - compile Cython with specific compiler 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,
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.
- Python setuptools' MANIFEST.in explained
- Python MANIFEST.in and graft – Hyesun Choi – Not First But Best
The manifest template commands are:
| Command | Description |
| | include all files matching any of the listed patterns |
| | exclude all files matching any of the listed patterns |
| | include all files under dir matching any of the listed patterns |
| | exclude all files under dir matching any of the listed patterns |
| | include all files anywhere in the source tree matching any of the listed patterns |
| | exclude all files anywhere in the source tree matching any of the listed patterns |
| | exclude all files under 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
.devNsuffix) - zero or more alpha releases (denoted with a
.aNsuffix) - zero or more beta releases (denoted with a
.bNsuffix) - zero or more release candidates (denoted with a
.rcNsuffix)
pip and other modern Python package installers ignore pre-releases by default when deciding which versions of dependencies to install.
setup.cfg
- Configuring setuptools using setup.cfg files - setuptools 63.4.0.post20220803 documentation
- Github - type-serialize - setup.cfg
- Github - aiohttp - 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 inpip install fastapi[all], installs the optional dependency packages. - An example:
- Appending
[metadata] name = Package-A
[options.extras_require] PDF =
ReportLab>=1.2RXP
</syntaxhighlight>
-
- Note that pyproject.toml uses
[project.optional-dependencies]instead. - Dependencies Management in Setuptools - setuptools documentation # Optional dependencies
- PEP 685 - Comparison of extra names for optional distribution dependencies
- Note that pyproject.toml uses
-
classifiers:- See PyPI:Classifiers. -
attr:- Value is read from a module attribute.attr:supports callables and iterables; unsupported types are cast usingstr().- 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.
- 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,
-
file:- Value is read from a list of files and then concatenated
| INFORMATION |
| The |
install_requires file error
There is a file: syntax like the following:
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:
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:
- Write the functionality
- Create the basic package structure with flit, register it on TestPyPI, and test
- Actually publish to PyPI with flit
- Add a README and CHANGELOG
- Apply formatting (black), test coverage (coverage), code quality (flake8, pylint, mccabe), static analysis (mypy), etc. with tox
- Set up automatic builds with GitHub Actions, and on every commit test coverage with Codecov & code quality with Clode Climate
- Use make to quickly run only pylint, coverage, etc.
- Set up automatic publishing with flit in GitHub Actions
How to upload using twine
Troubleshooting
ModuleNotFoundError: No module named 'pkg resources'
Install the package.
See also
Favorite site
- Python: Installing modules using setup.py
- Starting a Python project - Distutils
- What is Setup.py?
- (Flask documentation translation) Deploying with Setuptools
- Distributing a Python package
- Making a Python PIP Install package. Starting to make a PIP Install package with reference to the link. | by Jay | Medium
- [Recommended] Packaging and distributing projects — Python Packaging User Guide
- 4. Creating a Source Distribution — Python 3.10.0b3 documentation
Guide
Samples
- [Recommended] Github - pypa sample project