PubGrub-based dependency resolver for Python packages
26
stars
2,403
commits
Python
primary language
Sep 6, 2026
updated
nab is an experimental Python packaging lock and package download tool, aiming to have similar resolver performance to uv, while being written in Python.
nab reads a pyproject.toml, resolves the dependency tree, and
writes a pinned set of versions or a PEP 751 lockfile. It does not
install. Hand the lockfile to whatever installer you trust.
For package hygiene, and security reasons, the preference is to install nab itself as a tool, e.g.
Via pipx:
pipx install nab
Or via uv:
uv tool install nab
# pyproject.toml
[project]
name = "example"
version = "0.1.0"
dependencies = [
"starlette<=0.36.0",
"fastapi<=0.115.2",
]
nab lock pyproject.toml
Writes pylock.toml next to the project. For a pip-style
requirements list instead, use
nab lock --format requirements-without-hashes --output -.
nab makes some opinionated choices to be secure first
By default nab tries to extract static metadata, even from sdists, but sometimes that is not possible and you have to build a package to extract the dependency metadata. There are three build policies:
[[tool.nab.local-sources]] entries
and workspace members when their pyproject.toml cannot be read
statically[[tool.nab.vcs-sources]] clones,
[[tool.nab.archive-sources]] trees, and sdists from an index. It
is recommended that this only be turned on via per-package overridenab does not currently support sourcing the same package from distinct indexes. Indexes are processed in the order they are given to nab, and the first index that has a package is the only index that nab will source that package.
You can override this behavior by pinning specific packages to specific behavior.
You can also list different urls as a mirror for the same index. When a lockfile is written the primary url will always be used so that the lockfile will be stable, even if mirrors are used (this feature is a work in progress).
By default nab refuses every git URL, pinned or not:
[tool.nab.vcs]
policy = "block"
allowed-schemes = []
allowed-repos = []
require-pin = true
Each of the first three refuses everything until you set it:
policy: set to allow to consider git URLs at allallowed-schemes: the schemes you accept, e.g. git+httpsallowed-repos: the repository prefixes you accept, e.g.
https://github.com/myorg/require-pin is on by default, so a URL has to carry a
40-character commit hash and a floating branch or tag is
refused.
A package is then taken from a repository through a
[[tool.nab.vcs-sources]] entry. A pkg @ git+... requirement
under [project].dependencies gets the same admission checks,
but nab cannot resolve that form yet, so use a source entry.
Pre-release versions are selected if there are no stable versions to select given the requirements, even for transitive dependencies. A user option to force allow or block pre-releases per-package is a work in progress.
By default when a distribution is chosen the dependencies from that distribution are used, nab does not assume two different distributions for the same package version will have the same dependencies.
However, sometimes you may want the lock file to produce an sdist, that sdist may not have static metadata, and you don't want to wait for the sdist to build on every lock, there is a distribution policy of "sdist-install", that is the metadata will be taken from an appropriate wheel, but the sdist will be selected for the install.
This project includes multiple libraries that can be used by other tools:
nab-resolver: An agnostic resolver library based on PubGrub, but with
extensions that make it compatible with Python packaging standardsnab-provider: The Python packaging provider that drives the nab-resolver,
with lots of specific features and optimizations for the Python packaging
ecosystem. It does no I/O: everything comes through one interface a host
implementsnab-index: Provides APIs for talking to Python package indexes, abstracts
HTTP library interface so different HTTP libraries can be plugged innab-project: nab's own host. It implements the fetch interface over
nab-index and adds the resolve orchestration, the config ladder, workspace
discovery, the build path, the lockfile emitter and the downloaderAll 4 libraries are in experimental mode, I currently recommend pinning them,
e.g. nab-resolver==0.0.1, as APIs may change at any point.
Once we reach 0.1.0 we will only break API stability on each minor update,
so you will be able to pin to ==0.1.* or ~=0.1.0.
2,396 commits
7 commits
Python
99.9%
PubGrub-based dependency resolver for Python packages
26
stars
2,403
commits
Python
primary language
Sep 6, 2026
updated
nab is an experimental Python packaging lock and package download tool, aiming to have similar resolver performance to uv, while being written in Python.
nab reads a pyproject.toml, resolves the dependency tree, and
writes a pinned set of versions or a PEP 751 lockfile. It does not
install. Hand the lockfile to whatever installer you trust.
For package hygiene, and security reasons, the preference is to install nab itself as a tool, e.g.
Via pipx:
pipx install nab
Or via uv:
uv tool install nab
# pyproject.toml
[project]
name = "example"
version = "0.1.0"
dependencies = [
"starlette<=0.36.0",
"fastapi<=0.115.2",
]
nab lock pyproject.toml
Writes pylock.toml next to the project. For a pip-style
requirements list instead, use
nab lock --format requirements-without-hashes --output -.
nab makes some opinionated choices to be secure first
By default nab tries to extract static metadata, even from sdists, but sometimes that is not possible and you have to build a package to extract the dependency metadata. There are three build policies:
[[tool.nab.local-sources]] entries
and workspace members when their pyproject.toml cannot be read
statically[[tool.nab.vcs-sources]] clones,
[[tool.nab.archive-sources]] trees, and sdists from an index. It
is recommended that this only be turned on via per-package overridenab does not currently support sourcing the same package from distinct indexes. Indexes are processed in the order they are given to nab, and the first index that has a package is the only index that nab will source that package.
You can override this behavior by pinning specific packages to specific behavior.
You can also list different urls as a mirror for the same index. When a lockfile is written the primary url will always be used so that the lockfile will be stable, even if mirrors are used (this feature is a work in progress).
By default nab refuses every git URL, pinned or not:
[tool.nab.vcs]
policy = "block"
allowed-schemes = []
allowed-repos = []
require-pin = true
Each of the first three refuses everything until you set it:
policy: set to allow to consider git URLs at allallowed-schemes: the schemes you accept, e.g. git+httpsallowed-repos: the repository prefixes you accept, e.g.
https://github.com/myorg/require-pin is on by default, so a URL has to carry a
40-character commit hash and a floating branch or tag is
refused.
A package is then taken from a repository through a
[[tool.nab.vcs-sources]] entry. A pkg @ git+... requirement
under [project].dependencies gets the same admission checks,
but nab cannot resolve that form yet, so use a source entry.
Pre-release versions are selected if there are no stable versions to select given the requirements, even for transitive dependencies. A user option to force allow or block pre-releases per-package is a work in progress.
By default when a distribution is chosen the dependencies from that distribution are used, nab does not assume two different distributions for the same package version will have the same dependencies.
However, sometimes you may want the lock file to produce an sdist, that sdist may not have static metadata, and you don't want to wait for the sdist to build on every lock, there is a distribution policy of "sdist-install", that is the metadata will be taken from an appropriate wheel, but the sdist will be selected for the install.
This project includes multiple libraries that can be used by other tools:
nab-resolver: An agnostic resolver library based on PubGrub, but with
extensions that make it compatible with Python packaging standardsnab-provider: The Python packaging provider that drives the nab-resolver,
with lots of specific features and optimizations for the Python packaging
ecosystem. It does no I/O: everything comes through one interface a host
implementsnab-index: Provides APIs for talking to Python package indexes, abstracts
HTTP library interface so different HTTP libraries can be plugged innab-project: nab's own host. It implements the fetch interface over
nab-index and adds the resolve orchestration, the config ladder, workspace
discovery, the build path, the lockfile emitter and the downloaderAll 4 libraries are in experimental mode, I currently recommend pinning them,
e.g. nab-resolver==0.0.1, as APIs may change at any point.
Once we reach 0.1.0 we will only break API stability on each minor update,
so you will be able to pin to ==0.1.* or ~=0.1.0.
2,396 commits
7 commits
Python
99.9%