Links
setup.cfg filespyproject.toml filesProject
setup() Keywordsdependency_linkszip_safe flagsetuptools commandspkg_resourcessdist) and how to improve reproducibilityNote
a full specification for the keywords supplied tosetup.cfg orsetup.py can be found atkeywords reference
Important
The examples provided here are only to demonstrate the functionalityintroduced. More metadata and options arguments need to be suppliedif you want to replicate them on your system. If you are completelynew to setuptools, theQuickstart section is a good place to start.
Setuptools provides powerful tools to handle package discovery, includingsupport for namespace packages.
Normally, you would specify the packages to be included manually in the following manner:
[options]#...packages=mypkgmypkg.subpkg1mypkg.subpkg2
setup(# ...packages=['mypkg','mypkg.subpkg1','mypkg.subpkg2'])
# ...[tool.setuptools]packages=["mypkg","mypkg.subpkg1","mypkg.subpkg2"]# ...
If your packages are not in the root of the repository or do not correspondexactly to the directory structure, you also need to configurepackage_dir:
[options]# ...package_dir==src# directory containing all the packages (e.g. src/mypkg, src/mypkg/subpkg1, ...)# ORpackage_dir=mypkg=lib# mypkg.module corresponds to lib/module.pymypkg.subpkg1=lib1# mypkg.subpkg1.module1 corresponds to lib1/module1.pymypkg.subpkg2=lib2# mypkg.subpkg2.module2 corresponds to lib2/module2.py# ...
setup(# ...package_dir={"":"src"}# directory containing all the packages (e.g. src/mypkg, src/mypkg/subpkg1, ...))# ORsetup(# ...package_dir={"mypkg":"lib",# mypkg.module corresponds to lib/module.py"mypkg.subpkg1":"lib1",# mypkg.subpkg1.module1 corresponds to lib1/module1.py"mypkg.subpkg2":"lib2",# mypkg.subpkg2.module2 corresponds to lib2/module2.py# ...})
[tool.setuptools]# ...package-dir={""="src"}# directory containing all the packages (e.g. src/mypkg1, src/mypkg2)# OR[tool.setuptools.package-dir]mypkg="lib"# mypkg.module corresponds to lib/module.py"mypkg.subpkg1"="lib1"# mypkg.subpkg1.module1 corresponds to lib1/module1.py"mypkg.subpkg2"="lib2"# mypkg.subpkg2.module2 corresponds to lib2/module2.py# ...
This can get tiresome really quickly. To speed things up, you can rely onsetuptools automatic discovery, or use the provided tools, as explained inthe following sections.
Important
Althoughsetuptools allows developers to create a very complex mappingbetween directory names and package names, it is better tokeep it simpleand reflect the desired package hierarchy in the directory structure,preserving the same names.
By defaultsetuptools will consider 2 popular project layouts, each one withits own set of advantages and disadvantages[1][2] asdiscussed in the following sections.
Setuptools will automatically scan your project directory looking for theselayouts and try to guess the correct values for thepackages andpy_modules configuration.
Important
Automatic discovery willonly be enabled if youdon’t provide anyconfiguration forpackages andpy_modules.If at least one of them is explicitly set, automatic discovery will not take place.
Note: specifyingext_modules might also prevent auto-discover fromtaking place, unless your opt intoConfiguring setuptools using pyproject.toml files (which willdisable the backward compatible behaviour).
The project should contain asrc directory under the project root andall modules and packages meant for distribution are placed inside thisdirectory:
project_root_directory├── pyproject.toml # AND/OR setup.cfg, setup.py├── ...└── src/ └── mypkg/ ├── __init__.py ├── ... ├── module.py ├── subpkg1/ │ ├── __init__.py │ ├── ... │ └── module1.py └── subpkg2/ ├── __init__.py ├── ... └── module2.py
This layout is very handy when you wish to use automatic discovery,since you don’t have to worry about other Python files or folders in yourproject root being distributed by mistake. In some circumstances it can bealso less error-prone for testing or when usingPEP 420-style packages.On the other hand you cannot rely on the implicitPYTHONPATH=. to fireup the Python REPL and play with your package (you will need aneditable install to be able to do that).
(also known as “adhoc”)
The package folder(s) are placed directly under the project root:
project_root_directory├── pyproject.toml # AND/OR setup.cfg, setup.py├── ...└── mypkg/ ├── __init__.py ├── ... ├── module.py ├── subpkg1/ │ ├── __init__.py │ ├── ... │ └── module1.py └── subpkg2/ ├── __init__.py ├── ... └── module2.py
This layout is very practical for using the REPL, but in some situationsit can be more error-prone (e.g. during tests or if you have a bunchof folders or Python files hanging around your project root).
To avoid confusion, file and folder names that are used by popular tools (orthat correspond to well-known conventions, such as distributing documentationalongside the project code) are automatically filtered out in the case offlat-layout:
Reserved package names
Reserved top-level module names
Warning
If you are using auto-discovery withflat-layout,setuptools willrefuse to createdistribution archives withmultiple top-level packages or modules.
This is done to prevent common errors such as accidentally publishing codenot meant for distribution (e.g. maintenance-related scripts).
Users that purposefully want to create multi-package distributions areadvised to useCustom discovery or thesrc-layout.
There is also a handy variation of theflat-layout for utilities/librariesthat can be implemented with a single Python file:
A standalone module is placed directly under the project root, instead ofinside a package folder:
project_root_directory├── pyproject.toml # AND/OR setup.cfg, setup.py├── ...└── single_file_lib.py
If the automatic discovery does not work for you(e.g., you want toinclude in the distribution top-level packages withreserved names such astasks,example ordocs, or you want toexclude nested packages that would be otherwise included), you can usethe provided tools for package discovery:
[options]packages=find:#orpackages=find_namespace:
fromsetuptoolsimportfind_packages# orfromsetuptoolsimportfind_namespace_packages
# ...[tool.setuptools.packages]find={}# Scanning implicit namespaces is active by default# ORfind={namespaces=false}# Disable implicit namespaces
Let’s start with the first tool.find: (find_packages()) takes a sourcedirectory and two lists of package name patterns to exclude and include, andthen returns a list ofstr representing the packages it could find. To useit, consider the following directory:
mypkg├── pyproject.toml # AND/OR setup.cfg, setup.py└── src ├── pkg1 │ └── __init__.py ├── pkg2 │ └── __init__.py ├── additional │ └── __init__.py └── pkg └── namespace └── __init__.py
To have setuptools to automatically include packages foundinsrc that start with the namepkg and notadditional:
[options]packages=find:package_dir==src[options.packages.find]where=srcinclude=pkg*# alternatively: `exclude = additional*`
Note
pkg does not contain an__init__.py file, thereforepkg.namespace is ignored byfind: (seefind_namespace: below).
setup(# ...packages=find_packages(where='src',include=['pkg*'],# alternatively: `exclude=['additional*']`),package_dir={"":"src"}# ...)
Note
pkg does not contain an__init__.py file, thereforepkg.namespace is ignored byfind_packages()(seefind_namespace_packages() below).
[tool.setuptools.packages.find]where=["src"]include=["pkg*"]# alternatively: `exclude = ["additional*"]`namespaces=false
Note
When usingtool.setuptools.packages.find inpyproject.toml,setuptools will considerimplicit namespaces by default whenscanning your project directory.To avoidpkg.namespace from being added to your package listyou can setnamespaces=false. This will prevent any folderwithout an__init__.py file from being scanned.
Important
include andexclude accept strings representingglob patterns.These patterns should match thefull name of the Python module (as if itwas written in animport statement).
For example if you haveutil pattern, it will matchutil/__init__.py but notutil/files/__init__.py.
The fact that the parent package is matched by the pattern will not dictateif the submodule will be included or excluded from the distribution.You will need to explicitly add a wildcard (e.g.util*)if you want the pattern to also match submodules.
setuptools providesfind_namespace: (find_namespace_packages())which behaves similarly tofind: but works with namespace packages.
Before diving in, it is important to have a good understanding of whatnamespace packages are. Here is a quick recap.
When you have two packages organized as follows:
/Users/Desktop/timmins/foo/__init__.py/Library/timmins/bar/__init__.py
If bothDesktop andLibrary are on yourPYTHONPATH, then anamespace package calledtimmins will be created automatically for you whenyou invoke the import mechanism, allowing you to accomplish the following:
>>>importtimmins.foo>>>importtimmins.bar
as if there is only onetimmins on your system. The two packages can thenbe distributed separately and installed individually without affecting theother one.
Now, suppose you decide to package thefoo part for distribution and startby creating a project directory organized as follows:
foo├── pyproject.toml # AND/OR setup.cfg, setup.py└── src └── timmins └── foo └── __init__.py
If you want thetimmins.foo to be automatically included in thedistribution, then you will need to specify:
[options]package_dir==srcpackages=find_namespace:[options.packages.find]where=src
find: won’t work becausetimmins doesn’t contain__init__.pydirectly, instead, you have to usefind_namespace:.
You can think offind_namespace: as identical tofind: except itwould count a directory as a package even if it doesn’t contain__init__.pyfile directly.
setup(# ...packages=find_namespace_packages(where='src'),package_dir={"":"src"}# ...)
When you usefind_packages(), all directories without an__init__.py file will be ignored.On the other hand,find_namespace_packages() will scan alldirectories.
[tool.setuptools.packages.find]where=["src"]
When usingtool.setuptools.packages.find inpyproject.toml,setuptools will considerimplicit namespaces by default whenscanning your project directory.
After installing the package distribution,timmins.foo would becomeavailable to your interpreter.
Warning
Please have in mind thatfind_namespace: (setup.cfg),find_namespace_packages() (setup.py) andfind (pyproject.toml) willscanall folders that you have in your project directory if you use aflat-layout.
If used naïvely, this might result in unwanted files being added to yourfinal wheel. For example, with a project directory organized as follows:
foo├── docs│ └── conf.py├── timmins│ └── foo│ └── __init__.py└── tests └── tests_foo └── __init__.py
final users will end up installing not onlytimmins.foo, but alsodocs andtests.tests_foo.
A simple way to fix this is to adopt the aforementionedsrc-layout,or make sure to properly configure theinclude and/orexcludeaccordingly.
Tip
Afterbuilding your package, you can have a look if allthe files are correct (nothing missing or extra), by running the followingcommands:
tartfdist/*.tar.gzunzip-ldist/*.whl
This requires thetar andunzip to be installed in your OS.On Windows you can also use a GUI program such as7zip.
The fact you can create namespace packages so effortlessly above is creditedtoPEP 420. It used to be morecumbersome to accomplish the same result. Historically, there were two methodsto create namespace packages. One is thepkg_resources style supported bysetuptools and the other one beingpkgutils style offered bypkgutils module in Python. Both are now considereddeprecated despite thefact they still linger in many existing packages. These two differ in manysubtle yet significant aspects and you can find out more onPython packaginguser guide.
pkg_resource style namespace package¶This is the methodsetuptools directly supports. Starting with the samelayout, there are two pieces you need to add to it. First, an__init__.pyfile directly under your namespace package directory that contains thefollowing:
__import__("pkg_resources").declare_namespace(__name__)
And thenamespace_packages keyword in yoursetup.cfg orsetup.py:
[options]namespace_packages=timmins
setup(# ...namespace_packages=['timmins'])
And your directory should look like this
foo├──pyproject.toml# AND/OR setup.cfg, setup.py└──src└──timmins├──__init__.py└──foo└──__init__.pyRepeat the same for other packages and you can achieve the same result asthe previous section.
pkgutil style namespace package¶This method is almost identical to thepkg_resource except that thenamespace_packages declaration is omitted and the__init__.pyfile contains the following:
__path__=__import__('pkgutil').extend_path(__path__,__name__)
The project layout remains the same andpyproject.toml/setup.cfg remains the same.
https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure
[2]https://blog.ionelmc.ro/2017/09/25/rehashing-the-src-layout/