Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
with:
enable-cache: false
- name: Install resolver dependencies
run: uv pip install --target .python-packages 'nab-index>=0.0.11' 'nab-project>=0.0.14'
run: uv pip install --target .python-packages 'nab==0.0.16'
- name: Check formatting
run: cargo fmt --all --check
- name: Check lints
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog/697.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix ``from-index`` with nab 0.0.16 by using the nab CLI package's full configuration parser and passing
explicit inputs and planned targets to the resolver. Preserve pyproject settings and index overrides,
and pin nab while its component APIs are experimental.
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ dynamic = [
"version",
]
dependencies = [
"nab-index>=0.0.11",
"nab-project>=0.0.14",
"nab==0.0.16",
"nab-index==0.0.16",
"nab-project==0.0.16",
]
urls.Changelog = "https://pipdeptree.readthedocs.io/en/latest/changelog.html"
urls.Documentation = "https://pipdeptree.readthedocs.io"
Expand Down
9 changes: 6 additions & 3 deletions rust/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tempfile::tempdir;
use crate::Error;
use crate::metadata::{Discovered, Package};

const RESOLVER_IMPORT_ERROR: &str = "The from-index subcommand requires nab-index and nab-project";
const RESOLVER_IMPORT_ERROR: &str = "The from-index subcommand requires nab";
const PYPI_NAME: &str = "pypi";
const PYPI_URL: &str = "https://pypi.org/simple";
const GIT_SCHEMES: [&str; 5] = ["git+https", "git+ssh", "git+http", "git+file", "git+git"];
Expand Down Expand Up @@ -480,7 +480,7 @@ impl<'py> ResolverModules<'py> {
Ok(Self {
multi_index: import("nab_index.multi_index")?,
transport: import("nab_index.urllib3_async_transport")?,
config: import("nab_project.config")?,
config: import("nab.config.model")?,
resolve: import("nab_project.resolve")?,
})
}
Expand Down Expand Up @@ -513,7 +513,10 @@ impl<'py> ResolverModules<'py> {
.call((&config,), Some(&kwargs))?;
}
let kwargs = PyDict::new(py);
kwargs.set_item("config", config)?;
let inputs = config.call_method0("resolve_inputs")?;
kwargs.set_item("inputs", inputs)?;
let targets = self.config.getattr("plan_targets")?.call1((&config,))?;
kwargs.set_item("targets", targets)?;
let transport = self.transport.getattr("Urllib3AsyncTransport")?.call0()?;
self.resolve
.getattr("resolve_for_targets")?
Expand Down
15 changes: 11 additions & 4 deletions rust/tests/public_api/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,25 @@ fn patch_resolver(python: Python<'_>) -> PyResult<()> {
r#"
from pathlib import Path
from unittest.mock import create_autospec
import json

from packaging.version import Version
from nab_project.config import NabProjectConfig, plan_targets
from nab_project.lockfile import TargetLock
from nab_project.resolve import ResolveResult, TargetResult
import nab_project.resolve as resolve_module

def resolved(path, transport, *, config):
indexes = [(index.name, index.url) for index in config.indexes]
def resolved(path, transport, *, targets, inputs):
indexes = [(index.name, index.url) for index in inputs.indexes]
text = path.read_text() + "\n--- indexes ---\n" + repr(indexes)
Path(resolve_module.capture).write_text(text)
target = plan_targets(NabProjectConfig())[0]
Path(resolve_module.capture).with_suffix(".json").write_text(json.dumps({
"resolution": inputs.resolution.value,
"build_policy": inputs.build_policy.value,
"default_groups": inputs.default_groups,
"constraints": inputs.constraints,
"python_versions": [target.python_version for target in targets],
}))
target = targets[0]
return ResolveResult(
targets=(target,),
target_results=[
Expand Down
74 changes: 63 additions & 11 deletions rust/tests/public_api/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,61 @@ fn resolves_pyproject_indexes(
);
}

#[rstest]
#[case::specific("[tool.nab.environment]\npython = '3.12'\n", &["3.12"])]
#[case::matrix("[tool.nab.matrix]\npython = '>=3.11,<3.13'\nplatforms = ['linux_x86_64']\n", &["3.11", "3.12"])]
fn preserves_full_nab_configuration(#[case] environment: &str, #[case] versions: &[&str]) {
let directory = tempdir().unwrap();
let project = directory.path().join("pyproject.toml");
let mode = if versions.len() == 1 {
"specific"
} else {
"universal"
};
fs::write(
&project,
format!(
"[project]\nname = 'demo'\ndependencies = []\n\
[dependency-groups]\ndev = ['parent']\n\
[tool.nab]\nmode = '{mode}'\nresolution = 'lowest'\nbuild-policy = 'never'\n\
default-groups = ['dev']\nconstraints = ['child<3']\n\
[[tool.nab.indexes]]\nname = 'own'\nurl = 'https://own.example/simple'\n{environment}"
),
)
.unwrap();
let capture = directory.path().join("capture.txt");
let output = with_python(|python| {
install_resolver(python, &capture).unwrap();
execute_with_python(
python,
&[
"--warn",
"silence",
"i",
"--pyproject",
project.to_str().unwrap(),
"--index-url",
"https://override.example/simple",
],
)
});
assert_eq!((output.code, output.stderr.as_str()), (0, ""));
let settings: serde_json::Value =
serde_json::from_str(&fs::read_to_string(capture.with_extension("json")).unwrap()).unwrap();
assert_eq!(
settings,
serde_json::json!({
"resolution": "lowest", "build_policy": "never", "default_groups": ["dev"],
"constraints": ["child<3"], "python_versions": versions,
})
);
assert!(
fs::read_to_string(capture)
.unwrap()
.ends_with("[('primary', 'https://override.example/simple')]")
);
}

#[rstest]
#[case::default(
&[],
Expand Down Expand Up @@ -475,33 +530,30 @@ fn resolves_environment_indexes(
);
}

#[test]
fn reports_missing_resolver_module() {
#[rstest]
#[case("nab_index.multi_index")]
#[case("nab.config.model")]
fn reports_missing_resolver_module(#[case] name: &str) {
let output = with_python(|python| {
let module = python.import("nab_index.multi_index").unwrap();
let module = python.import(name).unwrap();
let modules = python
.import("sys")
.unwrap()
.getattr("modules")
.unwrap()
.cast_into::<PyDict>()
.unwrap();
modules
.set_item("nab_index.multi_index", python.None())
.unwrap();
modules.set_item(name, python.None()).unwrap();

let output = execute_with_python(python, &["from-index", "demo"]);

modules.set_item("nab_index.multi_index", module).unwrap();
modules.set_item(name, module).unwrap();
output
});

assert_eq!(
(output.code, output.stderr.as_str()),
(
1,
"The from-index subcommand requires nab-index and nab-project\n",
)
(1, "The from-index subcommand requires nab\n",)
);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def test_render_explicit_extras(extras: bool | str) -> None:


def test_render_reverse() -> None:
result = pipdeptree.render(packages="nab-index", extras="active", reverse=True, depth=1)
result = pipdeptree.render(packages="nab", extras="active", reverse=True, depth=1)

assert "pipdeptree==" in result

Expand Down
42 changes: 42 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,48 @@
from pathlib import Path


@pytest.mark.parametrize("source", ["requirements", "pyproject"])
@pytest.mark.parametrize("constraint", ["local-demo<2", "local-demo<1"])
def test_from_index_with_real_nab_config(
entry_point: Callable[[Sequence[str] | None], int | None],
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
monkeypatch: pytest.MonkeyPatch,
source: str,
constraint: str,
) -> None:
for name in ("PIP_INDEX_URL", "UV_INDEX_URL", "PIP_EXTRA_INDEX_URL", "UV_EXTRA_INDEX_URL"):
monkeypatch.delenv(name, raising=False)
local = tmp_path / "local"
local.mkdir()
(local / "pyproject.toml").write_text(
'[project]\nname = "local-demo"\nversion = "1"\ndependencies = []\n', encoding="utf-8"
)
index = tmp_path / "index"
index.mkdir()
if source == "requirements":
(tmp_path / "constraints.txt").write_text(constraint, encoding="utf-8")
path = tmp_path / "requirements.txt"
path.write_text("-c constraints.txt\n-e ./local\n", encoding="utf-8")
else:
path = tmp_path / "pyproject.toml"
path.write_text(
'[project]\nname = "demo"\ndependencies = []\n'
'[dependency-groups]\ndev = ["local-demo"]\n'
f'[tool.nab]\nconstraints = ["{constraint}"]\n'
'default-groups = ["dev"]\nresolution = "lowest"\nbuild-policy = "never"\n'
'[tool.nab.workspace]\nmembers = ["local"]\n',
encoding="utf-8",
)
code = entry_point(["from-index", f"--{source}", str(path), "--index-url", index.as_uri()])
output = capsys.readouterr()
if constraint == "local-demo<2":
assert (code, output.out, output.err) == (0, "local-demo==1\n", "")
else:
assert code == 1
assert "local-demo" in output.err


@pytest.mark.parametrize(
("args", "expected"),
[
Expand Down
Loading