Skip to main content
Version: 2.25 (dev)

Project introspection

Finding insights in your project.


Pants provides several goals to provide insights into your project's structure.

Tip: Use xargs to pipe these goals into other Pants commands

For example:

$ pants dependents project/util.py | xargs pants test

See Advanced target selection for more info and other techniques to use the results.

list - find your project's targets

list will find all targets that match the arguments.

For example, to show all targets in your project:

❯ pants list ::
//:ansicolors
//:setuptools
helloworld:lib
helloworld:pex_binary
helloworld/__init__.py:lib
helloworld/main.py:lib
...

You can specify a file, which will find the target(s) owning that file:

❯ pants list helloworld/greet/greeting_test.py
helloworld/greet/greeting_test.py:tests

list often works well when paired with the --filter options from Advanced Target Selection, e.g. pants --filter-target-type=python_test list :: to find all your python_test targets.

dependencies - find a target's dependencies

Use dependencies to list all targets used directly by a target.

❯ pants dependencies helloworld:pex_binary
helloworld/main.py:lib

You can specify a file, which will run on the target(s) owning that file:

❯ pants dependencies helloworld/main.py:lib
//:ansicolors
helloworld/greet/greeting.py:lib
helloworld/main.py:lib

To include transitive dependencies—meaning the dependencies of the direct dependencies—use --transitive:

❯ pants dependencies --transitive helloworld/main.py:lib
//:ansicolors
//:setuptools
//:types-setuptools
helloworld/greet/greeting.py:lib
helloworld/greet:translations
helloworld/main.py:lib
helloworld/translator/translator.py:lib

dependents - find which targets depend on a target

The dependents goal finds all targets that directly depend on the target you specify.

❯ pants dependents //:ansicolors
helloworld/main.py:lib

You can specify a file, which will run on the target(s) owning that file:

❯ pants dependents helloworld/translator/translator.py
helloworld/greet/greeting.py:lib
helloworld/translator:lib
helloworld/translator/translator_test.py:tests

To include transitive dependents — meaning targets that don't directly depend on your target, but which depend on a target that does directly use your target — use --transitive:

❯ pants dependents --transitive helloworld/translator/translator.py
helloworld:lib
helloworld:pex_binary
helloworld/main.py:lib
helloworld/greet:lib
...

To include the original target itself, use --closed:

❯ pants dependents --closed //:ansicolors
//:ansicolors
helloworld/main.py:lib

Export dependency graph

Both dependencies and dependents goals have the --format option allowing you to export data in multiple formats. Exporting information about the dependencies and dependents in JSON format will produce the adjacency list of your dependency graph:

$ pants dependencies --format=json \
helloworld/greet/greeting.py \
helloworld/translator/translator_test.py

{
"helloworld/greet/greeting.py:lib": [
"//:reqs#setuptools",
"//:reqs#types-setuptools",
"helloworld/greet:translations",
"helloworld/translator/translator.py:lib"
],
"helloworld/translator/translator_test.py:tests": [
"//:reqs#pytest",
"helloworld/translator/translator.py:lib"
]
}

This has various applications, and you could analyze, visualize, and process the data further. Sometimes, a fairly straightforward jq query would suffice, but for anything more complex, it may make sense to write a small program to process the exported graph. For instance, you could:

  • find tests with most transitive dependencies
$ pants dependencies --filter-target-type=python_test --format=json :: \
| jq -r 'to_entries[] | "\(.key)\t\(.value | length)"' \
| sort -k2 -n
  • find resources that only a few other targets depend on
$ pants dependents --filter-target-type=resource --format=json :: \
| jq -r 'to_entries[] | select(.value | length < 2)'
  • find files within the src/ directory that transitively lead to the most tests
# depgraph.py
import json

with open("data.json") as fh:
data = json.load(fh)

for source, dependents in data.items():
print(source, len([d for d in dependents if d.startswith("tests/")]))
$ pants dependents --transitive --format=json src:: > data.json
$ python3 depgraph.py | sort -k2 -n

For more sophisticated graph querying, you may want to look into graph libraries such as networkx. In a larger repository, it may make sense to track the health of the dependency graph and use the output of the graph export to identify parts of your codebase that would benefit from refactoring.

filedeps - find which files a target owns

filedeps outputs all of the files belonging to a target, based on its sources field.

❯ pants filedeps helloworld/greet:lib
helloworld/greet/BUILD
helloworld/greet/__init__.py
helloworld/greet/greeting.py

To output absolute paths, use the option --absolute:

$ pants filedeps --absolute helloworld/util:util
/Users/pantsbuild/example-python/helloworld/greet/BUILD
/Users/pantsbuild/example-python/helloworld/greet/__init__.py
/Users/pantsbuild/example-python/helloworld/greet/greeting.py

To include the files used by dependencies (including transitive dependencies), use --transitive:

$ pants filedeps --transitive helloworld/util:util
BUILD
helloworld/greet/BUILD
helloworld/greet/__init__.py
helloworld/greet/greeting.py
helloworld/greet/translations.json
...

peek - programmatically inspect a target

peek outputs JSON for each target specified.

$ pants peek helloworld/util:tests
[
{
"address": "helloworld/util:tests",
"target_type": "python_tests",
"dependencies": null,
"description": null,
"interpreter_constraints": null,
"skip_black": false,
"skip_docformatter": false,
"skip_flake8": true,
"skip_isort": false,
"skip_mypy": false,
"sources": [
"*.py",
"*.pyi",
"!test_*.py",
"!*_test.py",
"!tests.py",
"!conftest.py",
"!test_*.pyi",
"!*_test.pyi",
"!tests.pyi"
],
"tags": null
}
]

You can use --exclude-defaults for less verbose output:

$ pants peek --exclude-defaults helloworld/util:tests
[
{
"address": "helloworld/util:tests",
"target_type": "python_tests",
"skip_flake8": true,
}
]
Piping peek output into jq

peek can be particularly useful when paired with JQ to query the JSON. For example, you can combine pants peek with JQ to find all targets where you set the field skip_flake8=True:

$ pants peek :: | jq -r '.[] | select(.skip_flake8 == true) | .["address"]'
helloworld/greet:lib
helloworld/greet:tests
helloworld/util:lib
Piping other introspection commands into pants peek

Some introspection goals, such as filter, dependencies and dependents emit a flat list of target addresses. It's often useful to expand each of those into a full JSON structure with detailed properties of each target, by piping to pants peek:

pants dependents  helloworld/main.py:lib | xargs pants peek --exclude-defaults
[
{
"address": "helloworld:lib",
"target_type": "python_sources",
"dependencies": [
"helloworld/__init__.py:lib",
"helloworld/main.py:lib"
],
"sources": [
"helloworld/__init__.py",
"helloworld/main.py"
]
},
{
"address": "helloworld:pex_binary",
"target_type": "pex_binary",
"dependencies": [
"helloworld/main.py:lib"
],
"entry_point": {
"module": "main.py",
"function": null
}
}
]

Keep in mind, however, that the peek goal may be invoked by xargs as many times as necessary to use up the list of input items. This may break the structured data output, so it may be safer to use the --spec-files option.

paths - find dependency paths

paths emits a list of all dependency paths between two targets:

$ pants paths --from=helloworld/main.py --to=helloworld/translator/translator.py
[
[
"helloworld/main.py:lib",
"helloworld/greet/greeting.py:lib",
"helloworld/translator/translator.py:lib"
]
]

count-loc - count lines of code

count-loc counts the lines of code of the specified files by running the Succinct Code Counter tool.

❯ pants count-loc ::
───────────────────────────────────────────────────────────────────────────────
Language Files Lines Blanks Comments Code Complexity
───────────────────────────────────────────────────────────────────────────────
Python 1690 618679 23906 7270 587503 18700
HTML 61 6522 694 67 5761 0
JSON 36 18755 6 0 18749 0
YAML 30 2451 4 19 2428 0
JavaScript 6 671 89 8 574 32
CSV 1 2 0 0 2 0
JSONL 1 4 0 0 4 0
Jinja 1 11 0 0 11 2
Shell 1 13 2 2 9 4
TOML 1 146 5 0 141 0
───────────────────────────────────────────────────────────────────────────────
Total 1828 647254 24706 7366 615182 18738
───────────────────────────────────────────────────────────────────────────────
Estimated Cost to Develop $22,911,268
Estimated Schedule Effort 50.432378 months
Estimated People Required 53.813884
───────────────────────────────────────────────────────────────────────────────

SCC has dozens of options. You can pass through options by either setting --scc-args or using -- at the end of your command, like this:

pants count-loc :: -- --no-cocomo
See unexpected results? Set pants_ignore.

By default, Pants will ignore all globs specified in your .gitignore, along with dist/ and any hidden files.

To ignore additional files, add to the global option pants_ignore in your pants.toml, using the same syntax as .gitignore files.

For example:

pants.toml
[GLOBAL]
pants_ignore.add = ["/ignore_this_dir/"]