Pants handles dependencies with more precision than traditional Python workflows. Traditionally, you have a single heavyweight [virtual environment](🔗) that includes a large set of dependencies, whether or not you actually need them for your current task.
Instead, Pants understands exactly which dependencies every file in your project needs, and efficiently uses just that subset of dependencies needed for the task.
Among other benefits, this precise and automatic understanding of your dependencies gives you fine-grained caching. This means, for example, that if none of the dependencies for a particular test file have changed, the cached result can be safely used.
## Teaching Pants your "universe"(s) of dependencies
For Pants to know which dependencies each file uses, it must first know which specific dependencies are in your "universe", i.e. all the third-party dependencies your project directly uses.
By default, Pants uses a single universe for your whole project, but it's possible to set up multiple. See the header "Multiple resolves" in the "Lockfiles" section.
Each third-party dependency you directly use is modeled by a `
You do not need a `
python_requirement` target for transitive dependencies, i.e. requirements that you do not directly import.
To minimize boilerplate, Pants has target generators to generate `
python_requirement` targets for you:
python_requirements` for `
poetry_requirements` for Poetry projects.
python_requirements()` target generator parses a [`
requirements.txt`-style file](🔗) to produce a `
python_requirement` target for each entry.
If the file uses a different name than `
requirements.txt`, set `
source` like this:
Where should I put the `
You can name the file whatever you want, and put it wherever makes the most sense for your project.
In smaller repositories that only use Python, it's often convenient to put the file at the "build root" (top-level), as used on this page.
For larger repositories or multilingual repositories, it's often useful to have a `
3rdparty` or `
3rdparty/python` directory. Rather than the target's address being `
//:reqs#my_requirement`, its address would be `
3rdparty/python:reqs#my_requirement`, for example; or `
3rdparty/python#my_requirement` if you leave off the `
name` field for `
python_requirements`. See [Target Generation](🔗).
poetry_requirements()` target generator parses the [Poetry](🔗) section in `
pyproject.toml` to produce a `
python_requirement` target for each entry.
See the section "Lockfiles" below for how you can also hook up `
poetry.lock` to Pants.
## How dependencies are chosen
Once Pants knows about your "universe"(s) of dependencies, it determines which subset should be used through [dependency inference](🔗). Pants will read your import statements, like `
import django`, and map it back to the relevant `
python_requirement` target. Run [`
./pants dependencies path/to/file.py`](🔗) or `
./pants dependencies path/to:target` to confirm this works.
If dependency inference does not work—such as because it's a runtime dependency you do not import—you can explicitly add the `
python_requirement` target to the `
dependencies` field, like this:
### Use `
modules` and `
module_mapping` when the module name is not standard
Some dependencies expose a module different than their project name, such as `
beautifulsoup4` exposing `
bs4`. Pants assumes that a dependency's module is its normalized name—i.e. `
My-distribution` exposes the module `
my_distribution`. If that default does not apply to a dependency, it will not be inferred.
Pants already defines a [default module mapping](🔗) for some common Python requirements, but you may need to augment this by teaching Pants additional mappings:
If the dependency is a type stub, and the default does not work, set `
type_stub_modules` on the `
python_requirement` target, and `
type_stubs_module_mapping` on the `
python_requirements` and `
poetry_requirements` target generators. (The default for type stubs is to strip off `
-stubs`, and `
stubs-`. So, `
types-requests` gives type stubs for the module `
### Warning: multiple versions of the same dependency
It's invalid in Python to have conflicting versions of the same requirement, e.g. `
Django==2` and `
Django==3`. Instead, Pants supports "multiple resolves" (i.e. multiple lockfiles), as explained in the below section on lockfiles.
When you have multiple targets for the same dependency and they belong to the same resolve ("lockfile"), dependency inference will not work due to ambiguity. If you're using lockfiles—which we strongly recommend—the solution is to set the `
resolve` field for problematic `
python_requirement` targets so that each resolve has only one requirement and there is no ambiguity.
This ambiguity is often a problem when you have 2+ `
requirements.txt` or `
pyproject.toml` files in your project, such as `
project1/requirements.txt` and `
project2/requirements.txt` both specifying `
django`. You may want to set up each `
python_requirements` target generator to use a distinct resolve so that there is no overlap. Alternatively, if the versions are the same, you may want to consolidate the requirements into a common file.
We strongly recommend using lockfiles because they make your builds [more stable](🔗) so that new releases of dependencies will not break your project. They also reduce the risk of [supply chain attacks](🔗).
Pants has two types of lockfiles:
User lockfiles, for your own code such as packaging binaries and running tests.
Tool lockfiles, to install tools that Pants runs like Pytest and Flake8.
With both types of lockfiles, Pants can generate the lockfile for you with the `
### User lockfiles
First, set `
[python].enable_resolves` in `
By default, Pants will write the lockfile to `
3rdparty/python/default.lock`. If you want a different location, change `
[python].resolves` like this:
Then, use `
./pants generate-lockfiles` to generate the lockfile.
FYI: user lockfiles improve performance
As explained at the top of these docs, Pants only uses the subset of the "universe" of your dependencies that is actually needed for a build, such as running tests and packaging a wheel file. This gives fine-grained caching and has other benefits like built packages (e.g. PEX binaries) only including their true dependencies. However, naively, this would mean that you need to resolve dependencies multiple times, which can be slow.
If you use the default of Pex-generated lockfiles (see below), Pants will only install the subset of the lockfile you need for a task. If you instead use Poetry-generated lockfiles, Pants will first install the entire lockfile and then it will [extract](🔗) the exact subset needed.
This greatly speeds up performance and improves caching for goals like `
package`, and `
#### Multiple lockfiles
While it's often desirable to have a single lockfile for the whole repository for simplicity and consistency, sometimes you may need multiple. This is necessary, for example, when you have conflicting versions of requirements, such as one project using Django 2 and other projects using Django 3.
Start by defining multiple "resolves", which are logical names for lockfile paths. For example:
Then, teach Pants which resolves every `
python_requirement` target belongs to through the `
resolve` field. It will default to `
If you want the same requirement to show up in multiple resolves, use the [`
Then, run `
./pants generate-lockfiles` to generate the lockfiles. If the results aren't what you'd expect, adjust the prior step.
Finally, update your first-party targets like `
python_source` / `
python_test` / `
python_tests`, and `
pex_binary` to set their `
resolve` field. As before, the `
resolve` field defaults to `
If a first-party target is compatible with multiple resolves—such as some utility code—you can either use the [`
parametrize` mechanism](🔗) with the `
resolve` field or create distinct targets for the same entity.
All transitive dependencies of a target must use the same resolve. Pants's dependency inference already handles this for you by only inferring dependencies on targets that share the same resolve. If you incorrectly add a target from a different resolve to the `
dependencies` field, Pants will error with a helpful message when building your code with goals like `
package`, and `
### Tool lockfiles
Pants distributes a lockfile with each tool by default. However, if you change the tool's `
version` and `
extra_requirements`—or you change its interpreter constraints to not be compatible with our default lockfile—you will need to use a custom lockfile. Set the `
lockfile` option in `
pants.toml` for that tool, and then run `
You can also run `
./pants generate-lockfiles --resolve=tool`, e.g. `
--resolve=flake8`, to only generate that tool's lockfile rather than generating all lockfiles.
To disable lockfiles entirely for a tool, set `
[tool].lockfile = "<none>"` for that tool. Although we do not recommend this!
### Pex vs. Poetry for lockfile generation
Pants defaults to using [Pex](🔗) to generate lockfiles, but you can
instead use [Poetry](🔗) by setting `
[python].lockfile_generator = "poetry"`
We generally recommend using the default of Pex, which has several benefits:
[python-repos]` if you have a custom index or repository other than PyPI.
Supports VCS (Git) requirements.
Faster performance when installing lockfiles. With Pex, Pants will only install the subset of the lockfile needed for a task; with Poetry, Pants will first install the lockfile and then extract the relevant subset.
Avoids an issue many users have with problematic environment markers for transitive requirements (see below).
However, it is very plausible there are still issues with Pex lockfiles because the Python ecosystem is so vast. Please open [bug reports](🔗)! If `
generate-lockfiles` fails—or the lockfile errors when installed during goals like `
test` and `
package`—you may need to temporarily use Poetry.
Alternatively, you can try to manually generate and manage lockfiles—change to the v2.10 version of these docs to see instructions.
Incremental migration from Poetry to Pex
Pants can understand lockfiles in either Pex's JSON format or Poetry's requirements.txt-style file, regardless of what you set `
[python].lockfile_generator` to. This means that you can have some lockfiles using a different format than the others.
To incrementally migrate, consider writing a script that dynamically sets the option `
--python-lockfile-generator`, like this:
Tip: if you write a script, set `
[generate-lockfiles].custom_command` to say how to run your script.
#### Poetry issue with environment markers
One of the issues with Poetry is that sometimes `
generate-lockfiles` will work, but then it errors when being installed due to missing transitive dependencies. This is especially common with user lockfiles. For example:
Usually, the transitive dependency is in the lockfile, but it doesn't get installed because it has nonsensical environment markers, like this:
For user lockfiles, the workaround is to treat the problematic transitive dependencies as direct inputs to the resolve by creating a `
python_requirement` target, which usually causes the lockfile generator to handle things correctly. For example:
For tool lockfiles, add the problematic transitive dependencies to `
[tool].extra_requirements`. For example:
Then, regenerate the lock with `
You can also try manually removing the problematic environment markers, although you will need to remember to do this again whenever re-running `
## Advanced usage
### Requirements with undeclared dependencies
Sometimes a requirement does not properly declare in its packaging metadata the other dependencies it depends on, so those will not be installed. It's especially common to leave off dependencies on `
setuptools`, which results in import errors like this:
To work around this, you can use the `
dependencies` field of `
python_requirement`, so that anytime you depend on your requirement, you also bring in the undeclared dependency.
If you are using the `
python_requirements` and `
poetry_requirements` target generators, you can use the `
overrides` field to do the same thing:
### Version control and local requirements
You can install requirements from version control using two styles:
pip's proprietary VCS-style requirements, e.g.
direct references from [PEP 440](🔗), e.g.
[email protected] git+https://github.com/django/django.git`
[email protected] git+https://github.com/django/[email protected]/2.1.x`
[email protected] git+https://github.com/django/[email protected]`
You can also install from local files using [PEP 440 direct references](🔗). You must use an absolute path to the file, and you should ensure that the file exists on your machine.
Local file requirements do not yet work with lockfiles
Pex lockfiles will soon support local file requirements.
In the meantime, the workaround is to host the files in a private repository / index and load it with `
Version control via SSH
When using version controlled direct references hosted on private repositories with SSH access:
...you may see errors like:
To fix this, Pants needs to be configured to pass relevant SSH specific environment variables to processes by adding the following to `
### Custom repositories
There are two mechanisms for setting up custom Python distribution repositories:
#### Simple repositories as defined by PEP 503
If your custom repo is of this type, i.e., "private PyPI", aka "cheese shop", use the option `
indexes` in the `
To exclusively use your custom index—i.e. to not use PyPI—use `
indexes = [..]` instead of `
indexes.add = [..]`.
#### A Pip findlinks repository
If your custom repo is of this type, use the option `
repos` in the `
Indexes are assumed to have a nested structure (like [http://pypi.org/simple](🔗)), whereas repos are flat lists of packages.
#### Authenticating to custom repos
To authenticate to custom repos, you may need to provide credentials (such as a username and password) in the URL.
You can use [config file `
to load the values via environment variables. This avoids checking in sensitive information to
Alternatively, you can hardcode the value in a private (not checked-in) [.pants.rc file](🔗) in each user's Pants repo, that sets this config for the user:
## Tip: use `
./pants export` to create a virtual environment for IDEs
See [Setting up an IDE](🔗) for more information on `
./pants export`. This will create a virtual environment for your user code for compatibility with the rest of the Python ecosystem, e.g. IDEs like Pycharm.