PEP 771 – Default Extras for Python Software Packages
- Author:
- Thomas Robitaille <thomas.robitaille at gmail.com>, Jonathan Dekhtiar <jonathan at dekhtiar.com>
- Sponsor:
- Pradyun Gedam <pradyunsg at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 13-Jan-2025
- Post-History:
- 15-Jan-2025, 06-Feb-2025, 09-Jun-2025, 15-Oct-2026
Table of Contents
- Abstract
- Motivation
- Rationale
- Specification
- Backward Compatibility
- Security Implications
- How to teach this
- Package end users
- Package authors
- Supporting older versions of package installers
- Avoiding the addition of many default dependencies
- Impact on downstream packages
- Default extras are not a substitute for good package design
- Inheriting from default extras
- Incompatible extras
- Circular dependencies
- Documenting packages with default extras
- Packaging repository maintainers
- Reference Implementation
- Rejected Ideas
- Copyright
Abstract
PEP 508 specifies a mini-language for
declaring package dependencies. One feature of this language is the ability to
specify extras, which are optional components of a distribution that, when
used, install additional dependencies. This PEP proposes a mechanism to allow
one or more extras to be installed by default if none are provided explicitly,
and specifies that an empty set of extras (package[]) requests a minimal
installation without any default extras.
Motivation
Various use cases for default extras and possible solutions in this PEP were discussed extensively on this discuss.python.org thread. These fall into two broad cases that provide the motivation for the present PEP.
Recommended but not required dependencies
Package maintainers often use extras to declare optional dependencies that
extend the functionality or performance of a package. In some cases, it can be
difficult to determine which dependencies should be required and which should be
categorized as extras. A balance must be struck between the needs of typical
users, who may prefer most features to be available by default, and users who
want minimal installations without large, optional dependencies. One solution
with existing Python packaging infrastructure is for package maintainers to
define an extra, for example one called recommended, which
includes all non-essential but suggested dependencies. Users are then instructed to
install the package using package[recommended], while those who prefer more
control can use package. However, in practice, many users are unaware
of the [recommended] syntax, placing the burden on them to know this for a
typical installation. Having a way to have recommended dependencies be installed
by default while providing a way for users to request a more minimal installation
would satisfy this use case, and this PEP describes a solution to this.
Examples of packages that demonstrate this pattern by encouraging users to include extra dependencies by default include:
Packages supporting multiple backends or frontends
Another common use case for using extras is to define different backends or frontends and dependencies that need to be installed for each backend or frontend. A package might need at least one backend or frontend to be installed in order to be functional, but may be flexible on which backend or frontend this is. Concrete examples of such frontends or backends include:
- The Qt frontend library, which can be provided by PyQt5, PyQt6, PySide2, or PySide6
- Backend databases, such as MySQL, PostgreSQL, SQLite
With current packaging standards, maintainers have to either
require one of the backends or frontends or require users
to always specify extras, e.g., package[backend], and therefore risk users
having an unusable installation if they only install package. Having a
way to specify one or more default backend or frontend and providing a way to
override these defaults would provide a much better experience for users, and
the approach described in this PEP will allow this.
Note that this PEP does not aim to address the issue of disallowing conflicting or incompatible extras, for example if a package requires exactly one frontend or backend package. There is currently no mechanism in Python packaging infrastructure to disallow conflicting or incompatible extras to be installed, and this PEP does not change that.
Examples of packages that require at least one backend or frontend to work and recommend a default extra to install a backend or frontend include:
In all three cases, installing the package without any extras results in a broken installation, and this is a commonly reported support issue for some of these packages.
Rationale
A number of possible solutions have been discussed extensively by the community for several years, including in this discuss.python.org thread as well as in numerous issues and pull requests. The solution that is presented below:
- is an opt-in solution which means that package maintainers can choose whether or not to use it
- is flexible enough to accommodate both of the major use cases described in Motivation
- re-uses the syntax from PEP 508
It is the only solution, out of all those discussed, that meets all three criteria.
An alternative approach using existing infrastructure – splitting a package into a core package and a meta-package (discussed in detail in Using a meta-package for recommended installations) – can technically achieve similar outcomes but imposes a significant and ongoing maintenance burden, introduces user-facing issues such as naming mismatches and unintuitive uninstallation behavior, and has proven impractical enough that many projects have chosen not to adopt it.
The concept of default-but-optional dependencies has prior art in other
ecosystems. Linux distributions such as Debian/Ubuntu and Fedora have long
distinguished between required (Depends) and recommended (Recommends)
dependencies, where recommended packages are installed by default but can be
excluded. The Rust/Cargo ecosystem has had
default features
for over 10 years, which are widely used.
Specification
Default-Extra Metadata Field
A new multiple-use metadata field, Default-Extra, will be added to the core package
metadata.
For this field, each entry must be a string specifying an extra that will be
automatically included when the package is installed without any extras being specified explicitly.
Only entries already specified in a Provides-Extra
entry can be used in a Default-Extra entry. Each entry must be normalized
following the same rules as Provides-Extra (see PEP 685) and must match
the corresponding Provides-Extra entry exactly.
Examples:
Default-Extra: recommended
Default-Extra: backend1
Default-Extra: backend2
Default-Extra: backend3
Since this introduces a new field in the core package metadata, this will require Metadata-Version to be bumped to the next minor version (2.7 at the time of writing).
New key in [project] metadata table
A new key will be added to the [project] table in project metadata as
originally defined in PEP 621 and now defined in the PyPA specifications. This key will be named
default-optional-dependencies with the following description:
- TOML type: Array of strings
- Corresponding core metadata field:
Default-Extra
Each string in default-optional-dependencies must be the name of an extra
defined in optional-dependencies,
and each extra in this array will be converted to a matching Default-Extra
entry in the core package metadata. Build backends must raise an error if any
of the strings do not match the name of a defined extra. Examples of valid
usage which would produce the example Default-Extra entries presented in the previous section are:
[project]
default-optional-dependencies = [
"recommended",
]
and:
[project]
default-optional-dependencies = [
"backend1",
"backend2",
"backend3"
]
As with other keys in the [project] table, default-optional-dependencies
may be listed in dynamic, in which case the build backend is responsible
for determining the default extras. Since its value is an array, it may also be
specified statically and listed in dynamic simultaneously, following the
rules for list-valued keys in the pyproject.toml specification.
In all cases the entries
must be validated against the final set of extras produced by the build
backend, including when optional-dependencies is itself dynamic.
Correspondingly, Default-Extra may be listed in the Dynamic field of a
source distribution’s core metadata.
Overriding default extras
If extras are explicitly given in a dependency specification, the default extras must be ignored. If a package is specified bare, that is with no extras and no brackets at all, the default extras must be installed.
For example, if a package defines an extra1 default extra as well as a
non-default extra2 extra, then if a user were to install the package with:
$ pip install package
the dependencies of the default extra1 extra would be included. If the user instead
installs the package with:
$ pip install package[extra2]
then the extra2 extra would be installed but the default extra1 extra
would be ignored.
If the same package is specified multiple times in an installation command or
dependency tree, the default extras must be installed if any of the instances of
the package are specified bare. For instance, consider installing spam,
whose dependency tree contains package several times:
spam
├── tomato
│ └── package[extra2]
└── egg
└── package
then the default extra must be installed because package appears at least
once bare.
An empty set of extras, such as package[], must be interpreted as a request
to install the package without any default extras. package[] neither
triggers nor prevents the installation of default extras: the default extras
are installed only if package also appears bare elsewhere in the dependency
tree, as described above. For example, a dependency tree containing only
package[] and package[extra2] results in extra2 being installed but
no default extras, since no instance of package is bare. package[]
therefore provides a standardized way of obtaining a minimal installation of a
package.
If a package is already installed without its default extras (for example,
because it was pulled in as a dependency via package[]), and a user
subsequently runs pip install package (or equivalent), the installer must
ensure that the default extras’ dependencies are installed. In other words,
a bare package specification must always be treated as requesting the
package with its default extras, regardless of whether the package is already
present in the environment.
Conversely, this PEP does not introduce any mechanism for removing packages. If a package was installed with its default extras, or with an explicitly specified extra, and is subsequently installed with a different set of extras, the dependencies that are no longer requested are not uninstalled. This is consistent with how extras behave today.
When multiple packages in a dependency tree define default extras, the above
rules apply independently to each package. For example, consider two packages
flask and werkzeug that both define default extras. If flask lists
werkzeug[] in its required dependencies and werkzeug (without [])
in a default extra, then installing flask would include werkzeug with
its own default extras (because the default extra causes werkzeug to appear
bare), while installing flask[] would include only werkzeug[] (no
default extras for either package). This follows from the rules above and
requires no special handling of nested default extras.
We also note that some tools such as pip currently ignore unrecognized extras, and emit a warning to the user to indicate that the extra has not been recognized, e.g.:
$ pip install package[non-existent-extra]
WARNING: package 3.0.0 does not provide the extra 'non-existent-extra'
...
For tools that behave like this (rather than raising an error), if an extra is
recognized as invalid in a dependency specification, it should be ignored, and
if all specified extras are invalid, then this should be considered equivalent
to package[] (rather than package) and not install any default extras.
Finally, we note (as also discussed in Relying on tooling to deselect any default extras) that package installers are allowed to implement their own options to control the above behavior, for example implementing an option that disables default extras for some or all packages regardless of where these packages appear in the dependency tree. If such tool-specific options are implemented, tool developers should make these opt-in, and users should experience the above PEP 771 behavior as the default.
Default extras do not change how dependencies within extras interact with platform compatibility. If a default extra includes a dependency that is only available on certain platforms, the dependency should use environment markers as with any other dependency, e.g.:
Requires-Dist: windows-only-package ; sys_platform == "win32"
The behavior of the installer when a dependency within a default extra cannot be satisfied on the current platform is the same as for any other extra.
Examples
In this section we take a look at the use cases described in the Motivation section and how these can now be addressed by using the specification outlined above.
Recommended dependencies and minimal installations
First, we consider the case of packages that want recommended but not strictly required dependencies installed by default, while also providing a way to only install the required dependencies.
In order to do this, a package maintainer would define an extra called
recommended containing the recommended but not required dependencies, and
would choose to have this be included as a default extra:
[project]
default-optional-dependencies = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"package1",
"package2"
]
If this package was called package, users installing package would
then get the equivalent of package[recommended]. Users could alternatively
install package[] which would install the package without the default extras.
To take one of the concrete examples from the Motivation
section, the astropy package defines a recommended extra that users are
currently instructed to install in the default installation instructions.
With this PEP, the recommended extra could be declared as being a default
extra:
[project]
default-optional-dependencies = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"scipy",
"..."
]
meaning that installing:
$ pip install astropy
would then install optional but recommended dependencies such as scipy. Advanced users who want a minimal install could then use:
$ pip install astropy[]
Packages requiring at least one backend or frontend
As described in Motivation, some packages may support multiple backends and/or frontends, and in some cases it may be desirable to ensure that there is always at least one backend or frontend package installed, as the package would be unusable otherwise. Concrete examples of this might include a GUI application that needs a GUI library to be present to be usable but is able to support different ones, or a package that can rely on different computational backends but needs at least one to be installed.
In this case, package maintainers could make the choice to define an extra for each backend or frontend, and provide a default, e.g.:
[project]
default-optional-dependencies = [
"backend1"
]
[project.optional-dependencies]
backend1 = [
"package1",
"package2"
]
backend2 = [
"package3"
]
If packages can support e.g. multiple backends at the same time, and some of the backends should always be installed, then the dependencies for these must be given as required dependencies rather than using the default extras mechanism.
To take one of the concrete examples mentioned in Motivation, the napari package
can make use of one of PyQt5, PyQt6, PySide2, or PySide6, and users currently
need to explicitly specify napari[all] in order to have one of these be installed,
or e.g., napari[pyqt5] to explicitly specify one of the frontend packages. Installing
napari with no extras results in a non-functional package. With this PEP, napari
could define the following configuration:
[project]
default-optional-dependencies = [
"pyqt5"
]
[project.optional-dependencies]
pyqt5 = [
"PyQt5",
"..."
]
pyside2 = [
"PySide2",
"..."
]
pyqt6 = [
"PyQt6",
"..."
]
pyside6 = [
"PySide6",
"..."
]
meaning that:
$ pip install napari
would work out-of-the-box, but there would still be a mechanism for users to explicitly specify a frontend, e.g.:
$ pip install napari[pyside6]
Supporting extras that should not remove default extras
An additional case we consider here is where a package maintainer wants to support the ability for users to opt in to non-default extras, without removing default extras. Essentially, they might want:
package[]to give an installation without any extraspackageto install recommended dependencies (in arecommendedextra)package[alternative]to not install default extras, but to install an alternative set of optional dependencies (in analternativeextra)package[additional]to install both recommended and additional dependencies (in anadditionalextra)
This could be achieved with e.g.:
[project]
default-optional-dependencies = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"package1",
"package2"
]
alternative = [
"package3"
]
additional = [
"package[recommended]",
"package4"
]
The ability for a package to reference itself in the extras is supported by existing Python packaging tools.
Once again considering a concrete example, astropy could with this PEP define a
recommended extra (as described in Recommended dependencies and minimal
installations). However, it also defines other extras, including for example
jupyter, which adds packages that
enhance the user experience inside Jupyter-based
environments. It is possible that users opting in to this extra would still want
recommended dependencies to be installed. In this case, the following
configuration would solve this case:
[project]
default-optional-dependencies = [
"recommended"
]
[project.optional-dependencies]
recommended = [
"scipy",
"..."
]
jupyter = [
"astropy[recommended]",
"ipywidgets",
"..."
]
Users installing:
$ pip install astropy[jupyter]
would then get the same as:
$ pip install astropy[recommended, jupyter]
Packages with multiple kinds of defaults
In some cases, it may be that packages need multiple kinds of defaults. As an example, in Packages requiring at least one backend or frontend, we considered the case of packages that have either backends or frontends, but in some cases, packages may have to support backends and frontends, and want to specify one or more default frontend and one or more default backend.
Ideally, one may want the following behavior:
$ pip install package # installs default backend and frontend
$ pip install package[] # installs no backends or frontends
$ pip install package[backend1] # installs backend1 and default frontend
$ pip install package[frontend2] # installs frontend2 and default backend
$ pip install package[backend1, frontend2] # installs backend1 and frontend2
However, this PEP chooses not to provide a mechanism for making it so that e.g., if
backend1 is specified, the default backend would be disabled, but the
default frontend would be enabled, since this adds complexity.
Maintainers should instead for now document that if a backend or frontend is explicitly specified, both backend and frontend need to be specified. Discoverability for users who want to do this should not be an issue, however, since users need to read the documentation in any case to find out what backends or frontends are available, so they can be shown at the same time how to properly use the extras for backends and frontends.
One option to increase user friendliness is that maintainers can create extras
called for example defaultbackend and defaultfrontend which do install
the default backend and frontend. They can then recommend that users do:
$ pip install package # installs default backend and frontend
$ pip install package[] # installs no backends or frontends
$ pip install package[backend1, defaultfrontend] # installs backend1 and default frontend
$ pip install package[defaultbackend, frontend2] # installs frontend2 and default backend
$ pip install package[backend1, frontend2] # installs backend1 and frontend2
This would allow (if desired) users to then get whatever the recommended backend is, even if that default changes in time.
This limitation – the inability to deselect individual defaults while keeping
others – is shared by Cargo’s default features in the Rust ecosystem, where
the only option is all-or-nothing via default-features = false. If there
is a desire to implement a better solution in the future, we believe this PEP
should not preclude this. For example, one could imagine in the future adding
the ability for an extra to specify which default extras it disables, and if
this is not specified then explicitly specified extras would disable all default
extras (consistent with the present PEP).
Depending on packages with default extras
The examples above focus on packages that define default extras. It is also
useful to illustrate how downstream packages can depend on packages with
default extras in their pyproject.toml. The following examples assume
that astropy has defined recommended as a default extra.
A downstream package that is happy with the default extras needs no special syntax:
[project]
dependencies = [
"astropy>=6.0",
]
A downstream library that wants only the minimal dependencies can use
package[]:
[project]
dependencies = [
"astropy[]>=6.0",
]
This syntax is backward-compatible: for versions of astropy that pre-date
the introduction of default extras, astropy[] is equivalent to astropy.
A downstream package can also request specific extras, which will override the defaults:
[project]
dependencies = [
"astropy[jupyter]>=6.0",
]
The same dependency specifiers can be used anywhere PEP 508 strings are accepted, and the rules in Overriding default extras apply identically. For example, in PEP 723 inline script metadata:
# /// script
# dependencies = [
# "astropy[]>=6.0",
# ]
# ///
or in PEP 735 dependency groups:
[dependency-groups]
docs = [
"astropy",
]
minimal-tests = [
"astropy[]",
]
In all of these cases, astropy requests the default extras and
astropy[] does not, regardless of whether the specifier appears in a
project’s dependencies, a script, a dependency group, a requirements file, or
on the command line.
Backward Compatibility
Packages not using default extras
Once support for this PEP is added to tools in the packaging ecosystem, packages that do not make use of default extras will continue to work as-is and there should be no break in compatibility.
Packages using default extras
Once packages start defining default extras, those defaults will only be honored with recent versions of packaging tools which implement this PEP, but those packages will remain installable with older packaging tools – with the main difference being that the default extras will not be installed automatically when older packaging tools are used.
As described in Supporting older versions of package installers, package authors need to carefully evaluate when and how they adopt default extras depending on their user base, since some actions, such as moving a required dependency to a default extra, will result in breakage for users of older installers. Authors are thus responsible for minimizing the impact on their users.
Change in meaning of a bare package name
This PEP changes the meaning of a bare package name (e.g. package without
any extras) for packages that define default extras: prior to this PEP,
installing package would install only the dependencies listed in
dependencies, whereas with this PEP it may additionally install the
dependencies associated with default extras.
It is worth noting, however, that in practice the dependencies listed in
dependencies have never strictly represented a minimal set. Package authors
have always had full discretion over what to include in dependencies, and
many packages include dependencies in dependencies that are not strictly
required for core functionality but are considered important for a good default
experience. There is currently no mechanism for users or downstream packages to
opt out of such dependencies. This PEP provides a way for authors to separate
truly required dependencies from recommended-but-optional ones, and introduces
package[] as a standardized way to obtain a minimal installation
– something that does not exist today.
This PEP is also opt-in: no existing package changes behavior until its maintainer explicitly adds default extras, which is no different in nature from other metadata changes authors routinely make, such as adding or removing required dependencies.
Security Implications
As noted in Packaging-related tools, tools that audit dependencies for
security purposes will need to take Default-Extra into account to
accurately report what will be installed. Beyond this, this PEP introduces no
security risks that do not already exist with required dependencies, since
package authors can already cause arbitrary additional packages to be
installed by listing them as required dependencies.
How to teach this
This section outlines information that should be made available to people in different groups in the community in relation to the implementation of this PEP. Some aspects described below will be relevant even before the PEP is fully implemented in packaging tools as there are some preparations that can be done in advance of this implementation to facilitate any potential transition later on. The groups covered below are:
Package end users
Package users should be provided with clear installation instructions that show what extras are available for packages and how they behave, for example explaining that by default some recommended dependencies or a given frontend or backend will be installed, and how to opt out of this or override defaults, depending on what is available.
Packaging repository maintainers
The impact on individuals who repackage Python libraries for different
distributions, such as conda, Homebrew, Linux package installers (such as apt and dnf) and
so on, needs to be considered. Not all package distributions have historically
had mechanisms that would line up with the approach described, although this is
evolving (for example, conda has recently added support for optional
dependencies via CEP 44).
There are two cases to consider here:
- In cases where the repackaging is done by hand, such as for a number of conda-forge
recipes, the introduction of default extras should not have a large impact
since manual decisions already have to be made as to which dependencies to
include (for example, the conda-forge recipe for the astropy package
mentioned in the Motivation includes all the
recommendeddependencies by default). - In cases where the repackaging is done in an automated way, distribution maintainers will need to carefully consider how to treat default extras, and this may imply a non-negligible amount of work and discussion.
While repackaging projects that use default extras requires decisions about
how to map them, this does not make repackaging impossible. Repackagers
already routinely handle concepts that have no direct equivalent in their
target system – for example, until recently extras had no equivalent in conda,
yet conda-forge has successfully packaged thousands of Python projects (with some
recipes including extra dependencies as required, others omitting them, and
others splitting packages into e.g. package-base and package). Some
distribution systems already have concepts that map naturally to default
extras, such as the Recommends field in Debian/Ubuntu and Fedora mentioned
in Rationale.
When an upstream project introduces a default extra, the repackager can choose to include those dependencies as required or omit them. In cases where the default extra contains dependencies that were previously required, the repackager may not need to change anything at all – their existing recipe continues to work, and they gain the option to make those dependencies optional if the target distribution supports this.
Default extras provide additional signal about how package authors would like their package to be installed for the majority of users, and this should inform repackaging decisions regardless of the target distribution.
Reference Implementation
The following repository contains a fully functional demo package that makes use of default extras. This makes use of modified branches of several packages:
In addition, this branch contains a modified version of the Flit package.
The implementations above are proofs-of-concept at this time and the existing changes have not yet been reviewed by the relevant maintainers. Nevertheless, they are functional enough to allow for interested maintainers to try these out.
Rejected Ideas
Using a meta-package for recommended installations
Using existing packaging tools and infrastructure, package maintainers who want
to provide a minimal installation for some users and a default non-minimal
installation for regular users (e.g. with recommended dependencies or a default
backend) can technically already achieve this if they are willing to distribute
two packages instead of one – for example package-core which would be the main package
with minimal dependencies, and package which would be a meta-package that
would depend on package-core with optional dependencies enabled.
Taking once again a concrete example from the Motivation
section, the astropy package defines a recommended extra that users are
currently instructed to install in the default installation instructions.
In principle, one could rename the existing astropy package to e.g. astropy-core
and then create a new astropy package which would be a meta-package that would
contain the following dependencies section:
dependencies = [
"astropy-core[recommended]"
]
Since users may want to pin or place version constraints on the astropy
meta-package (e.g. astropy>5.0), the meta-package would need to follow
the same versions as the core package, and would need to use strict pinning
in the dependency section, e.g.:
version = "7.1.0"
dependencies = [
"astropy-core[recommended]==7.1.0"
]
This idea may seem appealing because it is technically already feasible. However, in practice, many projects have opted not to do this, for a number of reasons, which we now take a look at. Some of these may not be applicable to future new projects, but some of them apply to all projects, old and new.
Mismatch between package and module name
In terms of naming, there are two main options for a package that wants to use the meta-package approach:
- The first option is for the existing package to be kept as-is, which means
that
packagewould provide the minimal installation, and to then create a new meta-package with a different name, such aspackage-all. However, this suffers from one of the problems that motivated this PEP in the first place – users are often not aware that they can do e.g.package[recommended], so in the same way, they might not realize thatpackage-allexists. This once again places the burden on the average user to discover this, rather than shifting some of the burden to more advanced users. - The second option is for the existing package to be renamed to e.g.
package-core, and for the new meta-package to be calledpackage. This is a better option than the first one, but is not ideal, as it then introduces a non-intuitive mismatch between the package name and module name, in thatpackage-coreprovides thepackagemodule, andpackagedoes not provide any module. An example of why this would lead to confusion is that an average user might think that uninstalling thepackagemodule would be done with e.g.:$ pip uninstall package
but this would not be the case (the
packagemodule would still work), and it may not be obvious to this user that thepackage-corepackage even exists.
Multiple repositories or monorepos
This approach requires either maintaining two repositories instead of one, or switching to using a monorepo which would contain both packages. Neither option is ideal:
- Splitting into two repositories places a long-term additional burden on
maintainers, who have to make sure that these stay in sync (in terms of
version but also other aspects such as extras, as will be discussed in
Synchronizing metadata). In addition, the naming issue mentioned in
Mismatch between package and module name has additional complications here
– either the names of the repositories match the packages, in which case any
user who has a checkout of the previous
packagerepository will need to update their remote URLs or any git clone URLs to point to thepackage-corerepository. The alternative is to preserve thepackagerepository to contain thepackage-corepackage, and have a different name for the meta-package, but this could lead to confusion. - Switching to a monorepo may be a significant change for some projects,
and it is not uncommon for tools to assume by default that a single repository
corresponds to a single package – while these can often be configured to then
work with a monorepo, it is an additional burden on the maintainers. In
addition, if the main package is moved to a sub-directory in the monorepo, any
user that is e.g. pip installing the package from the repository URL will need
to adjust this to install from a sub-directory (adding
subdirectory=to the repo URL), and any existing workflows that clone the repository and assume the previous layout would break.
Depending on the minimal package
Packages that need to depend on package versions that are older than the first
version where the split was done will not easily be able to depend on the
minimal package. Whereas with the main proposal in this PEP, downstream users
will be able to depend on e.g. package[]>version where version pre-dates
the introduction of default extras, with the splitting approach it will not be
possible for downstream users to depend on e.g. package-core>version, since
package-core did not previously exist.
A possible solution to this is for developers to release no-op metadata packages for all old versions of a package, but this is a significant additional burden on the maintainers.
Uninstallation
As alluded to when referring to naming issues in Mismatch between package and module name, uninstalling packages will no longer work the way users expect. A user doing:
$ pip uninstall package
will still be left with package-core, but may not realize it. This is
not only confusing, but is in effect a breaking change that may impact a number
of existing workflows.
Package distributions
Having two packages instead of one would increase the long-term maintenance cost of package distributions because two packages would have to be released instead of one, and in some cases this would introduce extra manual work at each release.
Synchronizing metadata
The main metadata that would be important to keep synchronized between the main package and the meta-package is the version. Anytime a new release of the core package is done, the meta-package would need to have its version updated as well as the version pinning for the core package in the dependencies.
In addition, all extras defined in the core package would need to be redefined
and kept in sync in the meta-package. For example, if package defines an
additional extra, users should still be able to install
package[additional], but users installing the package-core package should
also have the option of doing package-core[additional].
Other metadata that would need to be kept in sync includes for example author information and project URLs.
Summary
Overall, this solution would imply a significantly higher maintenance burden, not only in terms of initial set-up and transition (which could already be prohibitive for large established projects), but also in terms of long-term maintenance. This also has the potential for breaking user workflows (in particular around the issue of repositories, and e.g. uninstallation). For all these reasons, we do not consider it a compelling alternative to the present PEP.
Syntax for deselecting extras
One of the main competing approaches was as follows: instead of having defaults be unselected if any extras were explicitly provided, default extras would need to be explicitly unselected.
In this picture, a new syntax for unselecting extras would be introduced as an
extension of the mini-language defined in PEP 508. If a package defined
default extras, users could opt out of these defaults by using a minus sign
(-) before the extra name. The proposed syntax update would have been as follows:
extras_list = (-)?identifier (wsp* ',' wsp* (-)?identifier)*
Valid examples of this new syntax would have included, e.g.:
package[-recommended]package[-backend1, backend2]package[pdf, -svg]
However, there are two main issues with this approach:
- One would need to define a number of rules for how to interpret corner cases
such as if an extra and its negated version were both present in the same
dependency specification (e.g.
package[pdf, -pdf]) or if a dependency tree included bothpackage[pdf]andpackage[-pdf], and the rules would not be intuitive to users. - More critically, this would introduce new syntax into dependency specification, which means that if any package defined a dependency using the new syntax, it and any other package depending on it would no longer be installable by existing packaging tools, so this would be a major backward compatibility break.
For these reasons, this alternative was not included in the final proposal.
Adding a special entry in extras_require
A potential solution that has been explored as an alternative to introducing the
new Default-Extra metadata field would be to make use of an extra with a
‘special’ name.
One example would be to use an empty string:
Provides-Extra:
Requires-Dist: numpy ; extra == ''
The idea would be that dependencies installed as part of the ‘empty’ extras
would only get installed if another extra was not specified. An implementation
of this was proposed in https://github.com/pypa/setuptools/pull/1503, but it
was found that there would be no way to make this work without breaking
compatibility with existing usage. For example, packages using Setuptools via
a setup.py file can do:
setup(
...
extras_require={'': ['package_a']},
)
which is valid and equivalent to having package_a being defined in
install_requires, so changing the meaning of the empty string would
break compatibility.
In addition, no other string (such as 'default') can be used as a special
string since all strings that would be a backward-compatible valid extras name
may already be used in existing packages.
There have been suggestions of using the special Python value None, but
again this is not possible, because even though one can use None in a setup.py file,
this is not possible in declarative files such as setup.cfg or
pyproject.toml, and furthermore ultimately extras names have to be converted
to strings in the package metadata. Having:
Provides-Extra: None
would be indistinguishable from the string ‘None’ which may already be used as
an extra name in a Python package. If we were to modify the core metadata
syntax to allow non-string ‘special’ extras names, then we would be back to
modifying the core metadata specification, which is no better than
introducing Default-Extra.
Relying on tooling to deselect any default extras
An alternative to the package[] syntax to unselect extras would be to
implement this at the level of packaging tools. For instance, pip could include
an option such as:
$ pip install package --no-default-extras
This option could apply to all or specific packages, similar to
the --no-binary option, e.g.,:
$ pip install package --no-default-extras :all:
The advantage of this approach is that tools supporting default extras could
also support unselecting them. This approach would be similar to the --no-install-recommends
option for the apt tool.
However, this solution is not ideal on its own because it would not allow packages to specify themselves that they do not need some of the default extras of a dependency. It would also carry risks for users who might disable all default extras in a big dependency tree, potentially breaking packages in the tree that rely on default extras at any point.
Nevertheless, this PEP does not disallow this approach and it is up to the maintainers of different packaging tools to decide if they want to support this kind of option. It is a flag that could at the very least be useful for package maintainers who want to identify places in dependency trees where default extras are being relied on. However, if it is supported, it should be made clear that using this flag does not guarantee a functional environment.
Standardizing an opt-in extra name
During the discussion of this PEP, an alternative was proposed that would keep
the ability for package authors to designate recommended extras, but make it
opt-in rather than opt-out: a conventional extra name such as recommended
or full would be standardized to mean “the dependencies that a typical user
should install”, and users would be taught to install package[recommended].
The meaning of a bare package would then be unchanged, which would be fully
backward-compatible.
This approach was not adopted for the following reasons:
- It does not address the core problem described in Motivation: users who
do not know about extras, or do not read the installation instructions,
would still type
pip install packageand get an incomplete installation. This is precisely the situation that packages such as astropy (withastropy[recommended]) and napari (withnapari[all]) are in today, so standardizing the name would be at best a marginal improvement over the status quo. - As discussed in Adding a special entry in extras_require, any name that is a valid extra name may already be in use by existing packages with a different meaning, so a reserved name cannot be introduced without potential collisions, and any name that is not a valid extra name would require new syntax, with the compatibility problems described in Syntax for deselecting extras.
Declaring recommended extras as metadata only
A related alternative is to limit the standard to a way for package authors to
declare which extras are recommended, for example via a metadata field such
as Recommended-Extra, without specifying what installers should do with
this information. Tools would then be free to decide how to expose it, for
example by displaying a message such as “to install package with recommended
optional dependencies, use package[recommended]”, by prompting the user
interactively, or by providing an option to install recommended extras.
This approach has the appeal of leaving user-interface decisions to tools. However:
- Interactive prompts are not viable in the many non-interactive contexts in
which installers are used (continuous integration, containers, automated
deployments), and informational messages are easily missed, particularly in
verbose installer output. Neither delivers the key benefit of this PEP, which
is that a user who installs
packagegets a working installation with the recommended dependencies. - Because the behavior would be tool-dependent, the same dependency specification could result in different installed environments depending on which installer is used, which undermines reproducibility and makes behavior harder to document and teach. This PEP instead specifies a single behavior for all installers.
- For packages that are unusable without at least one backend or frontend, a recommendation is not sufficient: the package needs a default.
The metadata introduced by this PEP does provide the declarative information that this alternative asks for, so tools remain free to build additional user-facing features on top of it, as discussed in Relying on tooling to deselect any default extras.
Requiring one of several extras
For the multiple backends or frontends use case, it was suggested that a more natural solution would be a mechanism to express that a package requires one of several alternatives (a logical OR between dependencies or extras), with the installer selecting an alternative that is already present in the environment, or prompting the user otherwise.
This PEP does not pursue this approach because:
- A logical OR between dependencies cannot be expressed in the dependency specification mini-language defined in PEP 508 without introducing new syntax, with the backward-compatibility problems described in Syntax for deselecting extras.
- It would require installers to make choices based on the current state of the environment, which makes the result of an installation depend on context and is difficult to reconcile with lock files and other reproducible resolution workflows.
- It would address only the backends and frontends use case, and not the recommended dependencies use case which motivates a large fraction of the interest in this PEP.
Default extras address the backends and frontends case by letting authors designate a default. A future PEP could build on the metadata introduced here to allow a set of extras to be declared as interchangeable alternatives, and nothing in this PEP precludes this.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.