Pip extras let you install optional dependency groups for a package.
Let’s say you have a package called my_awesome_package. It has a core set of dependencies that are always installed, but it also offers some additional features that require their own dependencies. For example, my_awesome_package might have a [web] extra for web-related functionality and a [dev] extra for development tools.
Here’s how you’d install just the core dependencies:
pip install my_awesome_package
To install the core dependencies and the web-related ones, you’d use:
pip install "my_awesome_package[web]"
And to get everything, including the development tools:
pip install "my_awesome_package[web,dev]"
This is incredibly useful for keeping your environments lean. You only install what you need. If you’re just using my_awesome_package as a library in your application, you probably don’t need the [dev] extras. If you’re developing my_awesome_package itself, you’ll definitely want [dev].
The magic behind this is in the setup.py or pyproject.toml of the package.
In setup.py, it looks like this:
from setuptools import setup, find_packages
setup(
name='my_awesome_package',
version='0.1.0',
packages=find_packages(),
install_requires=[
'requests', # Core dependency
'numpy', # Another core dependency
],
extras_require={
'web': [
'flask',
'gunicorn',
],
'dev': [
'pytest',
'flake8',
'black',
],
'all': [ # You can even define an 'all' extra
'flask', 'gunicorn', 'pytest', 'flake8', 'black'
]
},
)
In pyproject.toml (using setuptools build backend):
[project]
name = "my_awesome_package"
version = "0.1.0"
dependencies = [
"requests",
"numpy",
]
[project.optional-dependencies]
web = [
"flask",
"gunicorn",
]
dev = [
"pytest",
"flake8",
"black",
]
all = [
"flask",
"gunicorn",
"pytest",
"flake8",
"black",
]
When you run pip install "my_awesome_package[web]", pip reads the extras_require (or optional-dependencies) dictionary from the package metadata. It then installs the packages listed under the web key in addition to the base install_requires.
This is how libraries like pandas offer [excel], [parquet], [plot] or how Django offers [gis], [test] – they all use extras to let you pick and choose the functionality you need.
Consider a scenario where you’re packaging a library that can optionally integrate with a specific database. Without extras, you’d either force users to install the database drivers even if they don’t use that integration, or you’d have to maintain separate packages (e.g., my_awesome_package and my_awesome_package-with-postgres). Extras provide a much cleaner solution.
When you install a package with extras, pip merges the core dependencies with the specified extra dependencies. It doesn’t replace the core dependencies; it adds to them. If an extra dependency is already a core dependency, pip simply notes that it’s already satisfied.
The [all] extra shown in the examples is a common pattern. It allows users to easily install all optional dependencies at once with a single argument, like pip install "my_awesome_package[all]". This is convenient for development or testing environments where you might want to ensure all features are available.
A subtle but important point is how pip resolves dependencies when extras are involved. Pip will try to find the latest compatible versions that satisfy all the required dependencies, both core and those from the extras you selected. This means that if requests (a core dependency) has a conflict with flask (an extra dependency), pip’s dependency resolver will attempt to find versions of both that work together. If it can’t, the installation will fail, and pip will usually give you a clear message about the conflicting requirements.
The next step in managing dependencies is understanding environment markers, which allow for even more fine-grained control over which dependencies are installed based on the Python version, operating system, and other factors.