Skip to content

Commit ac0e74f

Browse files
authored
fix(index): support nab 0.0.17 (#704)
Fixes #697 by restoring index resolution with current nab releases. With nab-project 0.0.16 installed, `pipdeptree i pipdeptree` reports missing resolver dependencies because it imports the removed configuration module. Require nab 0.0.17 and use its configuration model with the resolver's explicit targets and inputs. Remove the nab-project upper bound, align CI dependencies, and refresh the documented index examples. Raise the development dependency minimums and use the package name as the coverage target so free-threaded Python uses the correct installation path.
1 parent 2748cfa commit ac0e74f

10 files changed

Lines changed: 90 additions & 57 deletions

File tree

.github/workflows/check.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
with:
3131
enable-cache: false
3232
- name: Install resolver dependencies
33-
run: uv pip install --target .python-packages 'nab-index>=0.0.11' 'nab-project>=0.0.14,<0.0.16'
33+
run: uv pip install --target .python-packages 'nab>=0.0.17' 'nab-index>=0.0.17' 'nab-project>=0.0.17'
3434
- name: Check formatting
3535
run: cargo fmt --all --check
3636
- name: Check lints

docs/changelog/697.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``from-index`` with nab 0.0.17 by adapting to its configuration and resolver APIs.

docs/how-to/usage.rst

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ A single requirement resolves to its full tree:
5454
.. code-block:: console
5555
5656
$ pipdeptree from-index "starlette"
57-
starlette==1.3.1
58-
└── anyio [candidate: 4.14.2]
59-
└── idna [candidate: 3.18]
57+
starlette==1.6.0
58+
└── anyio [candidate: 4.15.1]
59+
├── idna [candidate: 3.19]
60+
└── typing-extensions [candidate: 4.16.0]
6061
6162
Several requirements resolve together into one graph, and a version specifier bounds the pick:
6263

@@ -65,16 +66,17 @@ Several requirements resolve together into one graph, and a version specifier bo
6566
6667
$ pipdeptree from-index "fastapi<=0.115.2" starlette
6768
fastapi==0.115.2
68-
├── pydantic [candidate: 2.13.4]
69-
│ ├── annotated-types [candidate: 0.7.0]
70-
│ ├── pydantic-core [candidate: 2.46.4]
69+
├── pydantic [candidate: 2.13.5]
70+
│ ├── annotated-types [candidate: 0.8.0]
71+
│ ├── pydantic-core [candidate: 2.46.5]
7172
│ │ └── typing-extensions [candidate: 4.16.0]
7273
│ ├── typing-extensions [candidate: 4.16.0]
73-
│ └── typing-inspection [candidate: 0.4.2]
74+
│ └── typing-inspection [candidate: 0.4.4]
7475
│ └── typing-extensions [candidate: 4.16.0]
7576
├── starlette [candidate: 0.40.0]
76-
│ └── anyio [candidate: 4.14.2]
77-
│ └── idna [candidate: 3.18]
77+
│ └── anyio [candidate: 4.15.1]
78+
│ ├── idna [candidate: 3.19]
79+
│ └── typing-extensions [candidate: 4.16.0]
7880
└── typing-extensions [candidate: 4.16.0]
7981
8082
Request extras with the ``name[extra]`` syntax. The resolver pulls the extra's dependencies into the tree. They appear
@@ -85,9 +87,9 @@ as children, such as ``pysocks`` below, with the pinned version from the resolve
8587
8688
$ pipdeptree from-index "requests[socks]"
8789
requests==2.34.2
88-
├── certifi [candidate: 2026.6.17]
89-
├── charset-normalizer [candidate: 3.4.9]
90-
├── idna [candidate: 3.18]
90+
├── certifi [candidate: 2026.7.22]
91+
├── charset-normalizer [candidate: 3.5.1]
92+
├── idna [candidate: 3.19]
9193
├── pysocks [candidate: 1.7.1]
9294
└── urllib3 [candidate: 2.7.0]
9395
@@ -98,7 +100,7 @@ requirement; a non-matching marker drops it. Quote the argument so the shell kee
98100
.. code-block:: console
99101
100102
$ pipdeptree from-index 'idna; python_version >= "3.10"'
101-
idna==3.18
103+
idna==3.19
102104
103105
Resolve from a requirements file
104106
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -195,37 +197,50 @@ The graph and render flags behave as they do for the default command. Emit JSON
195197
"package": {
196198
"key": "anyio",
197199
"package_name": "anyio",
198-
"candidate_version": "4.14.2"
200+
"candidate_version": "4.15.1"
199201
},
200202
"dependencies": [
201203
{
202204
"key": "idna",
203205
"package_name": "idna",
204-
"candidate_version": "3.18"
206+
"candidate_version": "3.19"
207+
},
208+
{
209+
"key": "typing-extensions",
210+
"package_name": "typing-extensions",
211+
"candidate_version": "4.16.0"
205212
}
206213
]
207214
},
208215
{
209216
"package": {
210217
"key": "idna",
211218
"package_name": "idna",
212-
"candidate_version": "3.18"
219+
"candidate_version": "3.19"
213220
},
214221
"dependencies": []
215222
},
216223
{
217224
"package": {
218225
"key": "starlette",
219226
"package_name": "starlette",
220-
"candidate_version": "1.3.1"
227+
"candidate_version": "1.6.0"
221228
},
222229
"dependencies": [
223230
{
224231
"key": "anyio",
225232
"package_name": "anyio",
226-
"candidate_version": "4.14.2"
233+
"candidate_version": "4.15.1"
227234
}
228235
]
236+
},
237+
{
238+
"package": {
239+
"key": "typing-extensions",
240+
"package_name": "typing-extensions",
241+
"candidate_version": "4.16.0"
242+
},
243+
"dependencies": []
229244
}
230245
]
231246
@@ -235,8 +250,8 @@ Trace why the resolver pulled a package in with ``--reverse`` (``-r``):
235250
.. code-block:: console
236251
237252
$ pipdeptree from-index "fastapi<=0.115.2" --reverse --packages anyio
238-
anyio==4.14.2
239-
└── starlette==0.40.0 [requires: anyio==4.14.2]
253+
anyio==4.15.1
254+
└── starlette==0.40.0 [requires: anyio==4.15.1]
240255
└── fastapi==0.115.2 [requires: starlette==0.40.0]
241256
242257
Other supported flags include ``-o mermaid``, the ``graphviz-*`` formats, ``--depth`` (``-d``), package filters,

docs/tutorial/getting-started.rst

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,10 @@ Ask what ``starlette`` brings along:
209209
.. code-block:: console
210210
211211
$ pipdeptree from-index "starlette"
212-
starlette==1.3.1
213-
└── anyio [candidate: 4.14.2]
214-
└── idna [candidate: 3.18]
212+
starlette==1.6.0
213+
└── anyio [candidate: 4.15.1]
214+
├── idna [candidate: 3.19]
215+
└── typing-extensions [candidate: 4.16.0]
215216
216217
Read this the same way as the tree from your environment. The top line is the requirement you asked for and the
217218
indented lines are its dependencies. Each edge shows the candidate version the resolver selected from PyPI:
@@ -226,16 +227,17 @@ pin or bound it. Bound ``fastapi`` and resolve it alongside ``starlette``; the r
226227
227228
$ pipdeptree from-index "fastapi<=0.115.2" starlette
228229
fastapi==0.115.2
229-
├── pydantic [candidate: 2.13.4]
230-
│ ├── annotated-types [candidate: 0.7.0]
231-
│ ├── pydantic-core [candidate: 2.46.4]
230+
├── pydantic [candidate: 2.13.5]
231+
│ ├── annotated-types [candidate: 0.8.0]
232+
│ ├── pydantic-core [candidate: 2.46.5]
232233
│ │ └── typing-extensions [candidate: 4.16.0]
233234
│ ├── typing-extensions [candidate: 4.16.0]
234-
│ └── typing-inspection [candidate: 0.4.2]
235+
│ └── typing-inspection [candidate: 0.4.4]
235236
│ └── typing-extensions [candidate: 4.16.0]
236237
├── starlette [candidate: 0.40.0]
237-
│ └── anyio [candidate: 4.14.2]
238-
│ └── idna [candidate: 3.18]
238+
│ └── anyio [candidate: 4.15.1]
239+
│ ├── idna [candidate: 3.19]
240+
│ └── typing-extensions [candidate: 4.16.0]
239241
└── typing-extensions [candidate: 4.16.0]
240242
241243
The resolver selects ``0.115.2`` for the top-level package and follows its dependencies. You can resolve a

pyproject.toml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[build-system]
22
build-backend = "mesonpy"
33
requires = [
4-
"meson>=1.11.2",
5-
"meson-python>=0.20",
6-
"ninja>=1.13",
4+
"meson>=1.12",
5+
"meson-python>=0.21",
6+
"ninja>=1.13.2",
77
]
88

99
[project]
@@ -45,8 +45,9 @@ dynamic = [
4545
"version",
4646
]
4747
dependencies = [
48-
"nab-index>=0.0.11",
49-
"nab-project<0.0.16,>=0.0.14",
48+
"nab>=0.0.17",
49+
"nab-index>=0.0.17",
50+
"nab-project>=0.0.17",
5051
]
5152
urls.Changelog = "https://pipdeptree.readthedocs.io/en/latest/changelog.html"
5253
urls.Documentation = "https://pipdeptree.readthedocs.io"
@@ -58,7 +59,7 @@ scripts.pipdeptree = "pipdeptree._runner:main"
5859
[dependency-groups]
5960
test = [
6061
"covdefaults>=2.3",
61-
"diff-cover>=10.3",
62+
"diff-cover>=10.5.1",
6263
"pluggy>=1.6",
6364
"pytest>=9.1.1",
6465
"pytest-cov>=7.1",
@@ -68,10 +69,10 @@ docs = [
6869
"sphinx>=9.1",
6970
"sphinx-copybutton>=0.5.2",
7071
"sphinx-inline-tabs>=2025.12.21.14",
71-
"sphinx-llm>=0.4.1",
72-
"sphinxcontrib-mermaid>=2.1",
72+
"sphinx-llm>=1",
73+
"sphinxcontrib-mermaid>=2.1.1",
7374
"sphinxcontrib-towncrier>=0.5.0a0",
74-
"towncrier>=25.8",
75+
"towncrier>=26.9",
7576
]
7677

7778
[tool.meson-python]

rust/src/index.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use tempfile::tempdir;
1414
use crate::Error;
1515
use crate::metadata::{Discovered, Package};
1616

17-
const RESOLVER_IMPORT_ERROR: &str = "The from-index subcommand requires nab-index and nab-project";
17+
const RESOLVER_IMPORT_ERROR: &str =
18+
"The from-index subcommand requires nab, nab-index and nab-project";
1819
const PYPI_NAME: &str = "pypi";
1920
const PYPI_URL: &str = "https://pypi.org/simple";
2021
const GIT_SCHEMES: [&str; 5] = ["git+https", "git+ssh", "git+http", "git+file", "git+git"];
@@ -480,7 +481,7 @@ impl<'py> ResolverModules<'py> {
480481
Ok(Self {
481482
multi_index: import("nab_index.multi_index")?,
482483
transport: import("nab_index.urllib3_async_transport")?,
483-
config: import("nab_project.config")?,
484+
config: import("nab.config.model")?,
484485
resolve: import("nab_project.resolve")?,
485486
})
486487
}
@@ -508,12 +509,12 @@ impl<'py> ResolverModules<'py> {
508509
let tuple = PyTuple::new(py, configs.iter())?;
509510
let kwargs = PyDict::new(py);
510511
kwargs.set_item("indexes", tuple)?;
511-
config = PyModule::import(py, "dataclasses")?
512-
.getattr("replace")?
513-
.call((&config,), Some(&kwargs))?;
512+
config = config.call_method("replace", (), Some(&kwargs))?;
514513
}
515514
let kwargs = PyDict::new(py);
516-
kwargs.set_item("config", config)?;
515+
let targets = self.config.getattr("plan_targets")?.call1((&config,))?;
516+
kwargs.set_item("targets", targets)?;
517+
kwargs.set_item("inputs", config.call_method0("resolve_inputs")?)?;
517518
let transport = self.transport.getattr("Urllib3AsyncTransport")?.call0()?;
518519
self.resolve
519520
.getattr("resolve_for_targets")?

rust/tests/public_api/common.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,15 @@ from pathlib import Path
137137
from unittest.mock import create_autospec
138138
139139
from packaging.version import Version
140-
from nab_project.config import NabProjectConfig, plan_targets
141140
from nab_project.lockfile import TargetLock
142141
from nab_project.resolve import ResolveResult, TargetResult
143142
import nab_project.resolve as resolve_module
144143
145-
def resolved(path, transport, *, config):
146-
indexes = [(index.name, index.url) for index in config.indexes]
144+
def resolved(path, transport, *, targets, inputs):
145+
indexes = [(index.name, index.url) for index in inputs.indexes]
147146
text = path.read_text() + "\n--- indexes ---\n" + repr(indexes)
148147
Path(resolve_module.capture).write_text(text)
149-
target = plan_targets(NabProjectConfig())[0]
148+
target = targets[0]
150149
return ResolveResult(
151150
targets=(target,),
152151
target_results=[

rust/tests/public_api/index.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ fn reports_missing_resolver_module() {
500500
(output.code, output.stderr.as_str()),
501501
(
502502
1,
503-
"The from-index subcommand requires nab-index and nab-project\n",
503+
"The from-index subcommand requires nab, nab-index and nab-project\n",
504504
)
505505
);
506506
}

tests/test_cli.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import tempfile
77
import webbrowser
88
from functools import partial
9-
from typing import TYPE_CHECKING
9+
from typing import TYPE_CHECKING, Final
1010
from unittest.mock import MagicMock, create_autospec
1111

1212
import pytest
@@ -220,6 +220,20 @@ def test_cli_lock(
220220
assert (entry_point(["from-lock", str(lock)]), "locked==1" in capsys.readouterr().out) == (0, True)
221221

222222

223+
def test_cli_index_empty_project(
224+
entry_point: Callable[[Sequence[str] | None], int | None],
225+
tmp_path: Path,
226+
capsys: pytest.CaptureFixture[str],
227+
) -> None:
228+
project: Final = tmp_path / "pyproject.toml"
229+
project.write_text('[project]\nname = "empty"\nversion = "1"\ndependencies = []\n')
230+
231+
code: Final = entry_point(["from-index", "--pyproject", str(project), "--json"])
232+
captured: Final = capsys.readouterr()
233+
234+
assert (code, json.loads(captured.out), captured.err) == (0, [], "")
235+
236+
223237
def test_module_entrypoint(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
224238
monkeypatch.setattr(sys, "argv", ["pipdeptree", "--version"])
225239

tox.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
requires = [ "tox>=4.57.1", "tox-uv>=1.35.2" ]
1+
requires = [ "tox>=4.61.2", "tox-uv>=1.36" ]
22
env_list = [
33
"3.15",
44
"3.14",
@@ -28,7 +28,7 @@ commands = [
2828
"{tty:--color=yes}",
2929
{ replace = "posargs", default = [
3030
"--cov",
31-
"{env_site_packages_dir}{/}pipdeptree",
31+
"pipdeptree",
3232
"--cov",
3333
"{tox_root}{/}tests",
3434
"--cov-config=pyproject.toml",
@@ -102,13 +102,13 @@ commands = [
102102
[env.fix]
103103
description = "format the code base and report remaining style violations"
104104
skip_install = true
105-
deps = [ "pre-commit-uv>=4.2.2" ]
105+
deps = [ "pre-commit-uv>=4.3" ]
106106
commands = [ [ "pre-commit", "run", "--all-files", "--show-diff-on-failure" ] ]
107107

108108
[env.pkg_meta]
109109
description = "check that the long description is valid"
110110
skip_install = true
111-
deps = [ "check-wheel-contents>=0.6.3", "twine>=6.2", "uv>=0.11.29" ]
111+
deps = [ "check-wheel-contents>=0.6.3", "twine>=7", "uv>=0.12.10" ]
112112
commands = [
113113
[ "uv", "build", "--sdist", "--wheel", "--out-dir", "{env_tmp_dir}", "." ],
114114
[ "twine", "check", "{env_tmp_dir}{/}*" ],
@@ -117,7 +117,7 @@ commands = [
117117

118118
[env.type]
119119
description = "run type check on code base"
120-
deps = [ "ty>=0.0.61" ]
120+
deps = [ "ty>=0.0.79" ]
121121
commands = [ [ "ty", "check", "--python-platform", "all", "--output-format", "concise", "--error-on-warning", "." ] ]
122122

123123
[env.dev]
@@ -161,6 +161,6 @@ commands_pre = [
161161
[env.release]
162162
description = "build the changelog for the next release, then commit and tag it"
163163
skip_install = true
164-
deps = [ "towncrier>=25.8" ]
164+
deps = [ "towncrier>=26.9" ]
165165
pass_env = [ "GH_TOKEN" ]
166166
commands = [ [ "python", "{tox_root}{/}tools{/}release.py", { replace = "posargs", extend = true } ] ]

0 commit comments

Comments
 (0)