mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-14 09:45:48 +00:00
## Introduction
PEP 621 is limited. Specifically, it lacks
* Relative path support
* Editable support
* Workspace support
* Index pinning or any sort of index specification
The semantics of urls are a custom extension, PEP 440 does not specify
how to use git references or subdirectories, instead pip has a custom
stringly format. We need to somehow support these while still stying
compatible with PEP 621.
## `tool.uv.source`
Drawing inspiration from cargo, poetry and rye, we add `tool.uv.sources`
or (for now stub only) `tool.uv.workspace`:
```toml
[project]
name = "albatross"
version = "0.1.0"
dependencies = [
"tqdm >=4.66.2,<5",
"torch ==2.2.2",
"transformers[torch] >=4.39.3,<5",
"importlib_metadata >=7.1.0,<8; python_version < '3.10'",
"mollymawk ==0.1.0"
]
[tool.uv.sources]
tqdm = { git = "https://github.com/tqdm/tqdm", rev = "cc372d09dcd5a5eabdc6ed4cf365bdb0be004d44" }
importlib_metadata = { url = "https://github.com/python/importlib_metadata/archive/refs/tags/v7.1.0.zip" }
torch = { index = "torch-cu118" }
mollymawk = { workspace = true }
[tool.uv.workspace]
include = [
"packages/mollymawk"
]
[tool.uv.indexes]
torch-cu118 = "https://download.pytorch.org/whl/cu118"
```
See `docs/specifying_dependencies.md` for a detailed explanation of the
format. The basic gist is that `project.dependencies` is what ends up on
pypi, while `tool.uv.sources` are your non-published additions. We do
support the full range or PEP 508, we just hide it in the docs and
prefer the exploded table for easier readability and less confusing with
actual url parts.
This format should eventually be able to subsume requirements.txt's
current use cases. While we will continue to support the legacy `uv pip`
interface, this is a piece of the uv's own top level interface. Together
with `uv run` and a lockfile format, you should only need to write
`pyproject.toml` and do `uv run`, which generates/uses/updates your
lockfile behind the scenes, no more pip-style requirements involved. It
also lays the groundwork for implementing index pinning.
## Changes
This PR implements:
* Reading and lowering `project.dependencies`,
`project.optional-dependencies` and `tool.uv.sources` into a new
requirements format, including:
* Git dependencies
* Url dependencies
* Path dependencies, including relative and editable
* `pip install` integration
* Error reporting for invalid `tool.uv.sources`
* Json schema integration (works in pycharm, see below)
* Draft user-level docs (see `docs/specifying_dependencies.md`)
It does not implement:
* No `pip compile` testing, deprioritizing towards our own lockfile
* Index pinning (stub definitions only)
* Development dependencies
* Workspace support (stub definitions only)
* Overrides in pyproject.toml
* Patching/replacing dependencies
One technically breaking change is that we now require user provided
pyproject.toml to be valid wrt to PEP 621. Included files still fall
back to PEP 517. That means `pip install -r requirements.txt` requires
it to be valid while `pip install -r requirements.txt` with `-e .` as
content falls back to PEP 517 as before.
## Implementation
The `pep508` requirement is replaced by a new `UvRequirement` (name up
for bikeshedding, not particularly attached to the uv prefix). The still
existing `pep508_rs::Requirement` type is a url format copied from pip's
requirements.txt and doesn't appropriately capture all features we
want/need to support. The bulk of the diff is changing the requirement
type throughout the codebase.
We still use `VerbatimUrl` in many places, where we would expect a
parsed/decomposed url type, specifically:
* Reading core metadata except top level pyproject.toml files, we fail a
step later instead if the url isn't supported.
* Allowed `Urls`.
* `PackageId` with a custom `CanonicalUrl` comparison, instead of
canonicalizing urls eagerly.
* `PubGrubPackage`: We eventually convert the `VerbatimUrl` back to a
`Dist` (`Dist::from_url`), instead of remembering the url.
* Source dist types: We use verbatim url even though we know and require
that these are supported urls we can and have parsed.
I tried to make improve the situation be replacing `VerbatimUrl`, but
these changes would require massive invasive changes (see e.g.
https://github.com/astral-sh/uv/pull/3253). A main problem is the ref
`VersionOrUrl` and applying overrides, which assume the same
requirement/url type everywhere. In its current form, this PR increases
this tech debt.
I've tried to split off PRs and commits, but the main refactoring is
still a single monolith commit to make it compile and the tests pass.
## Demo
Adding
d1ae3b85d5/pyproject.json
as json schema (v7) to pycharm for `pyproject.toml`, you can try the IDE
support already:

[dove.webm](c293c272-c80b-459d-8c95-8c46a8d198a1)
788 lines
22 KiB
Rust
788 lines
22 KiB
Rust
#![cfg(feature = "pypi")]
|
|
|
|
//! Integration tests for the resolver. These tests rely on a live network connection, and hit
|
|
//! `PyPI` directly.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::str::FromStr;
|
|
|
|
use anyhow::Result;
|
|
use chrono::{DateTime, Utc};
|
|
use once_cell::sync::Lazy;
|
|
|
|
use distribution_types::{IndexLocations, Requirement, Resolution, SourceDist};
|
|
use pep508_rs::{MarkerEnvironment, StringVersion};
|
|
use platform_tags::{Arch, Os, Platform, Tags};
|
|
use uv_cache::Cache;
|
|
use uv_client::RegistryClientBuilder;
|
|
use uv_configuration::{BuildKind, Constraints, NoBinary, NoBuild, Overrides, SetupPyStrategy};
|
|
use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment};
|
|
use uv_resolver::{
|
|
DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options,
|
|
OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver,
|
|
};
|
|
use uv_types::{
|
|
BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, SourceBuildTrait,
|
|
};
|
|
|
|
// Exclude any packages uploaded after this date.
|
|
static EXCLUDE_NEWER: Lazy<ExcludeNewer> = Lazy::new(|| {
|
|
ExcludeNewer::from(
|
|
DateTime::parse_from_rfc3339("2023-11-18T12:00:00Z")
|
|
.unwrap()
|
|
.with_timezone(&Utc),
|
|
)
|
|
});
|
|
|
|
struct DummyContext {
|
|
cache: Cache,
|
|
interpreter: Interpreter,
|
|
index_locations: IndexLocations,
|
|
}
|
|
|
|
impl DummyContext {
|
|
fn new(cache: Cache, interpreter: Interpreter) -> Self {
|
|
Self {
|
|
cache,
|
|
interpreter,
|
|
index_locations: IndexLocations::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl BuildContext for DummyContext {
|
|
type SourceDistBuilder = DummyBuilder;
|
|
|
|
fn cache(&self) -> &Cache {
|
|
&self.cache
|
|
}
|
|
|
|
fn interpreter(&self) -> &Interpreter {
|
|
&self.interpreter
|
|
}
|
|
|
|
fn build_isolation(&self) -> BuildIsolation {
|
|
BuildIsolation::Isolated
|
|
}
|
|
|
|
fn no_build(&self) -> &NoBuild {
|
|
&NoBuild::None
|
|
}
|
|
|
|
fn no_binary(&self) -> &NoBinary {
|
|
&NoBinary::None
|
|
}
|
|
|
|
fn setup_py_strategy(&self) -> SetupPyStrategy {
|
|
SetupPyStrategy::default()
|
|
}
|
|
|
|
fn index_locations(&self) -> &IndexLocations {
|
|
&self.index_locations
|
|
}
|
|
|
|
async fn resolve<'a>(&'a self, _: &'a [Requirement]) -> Result<Resolution> {
|
|
panic!("The test should not need to build source distributions")
|
|
}
|
|
|
|
async fn install<'a>(&'a self, _: &'a Resolution, _: &'a PythonEnvironment) -> Result<()> {
|
|
panic!("The test should not need to build source distributions")
|
|
}
|
|
|
|
async fn setup_build<'a>(
|
|
&'a self,
|
|
_: &'a Path,
|
|
_: Option<&'a Path>,
|
|
_: &'a str,
|
|
_: Option<&'a SourceDist>,
|
|
_: BuildKind,
|
|
) -> Result<Self::SourceDistBuilder> {
|
|
Ok(DummyBuilder)
|
|
}
|
|
}
|
|
|
|
struct DummyBuilder;
|
|
|
|
impl SourceBuildTrait for DummyBuilder {
|
|
async fn metadata(&mut self) -> Result<Option<PathBuf>> {
|
|
panic!("The test should not need to build source distributions")
|
|
}
|
|
|
|
async fn wheel<'a>(&'a self, _: &'a Path) -> Result<String> {
|
|
panic!("The test should not need to build source distributions")
|
|
}
|
|
}
|
|
|
|
async fn resolve(
|
|
manifest: Manifest,
|
|
options: Options,
|
|
markers: &'static MarkerEnvironment,
|
|
tags: &Tags,
|
|
) -> Result<ResolutionGraph> {
|
|
let client = RegistryClientBuilder::new(Cache::temp()?).build();
|
|
let flat_index = FlatIndex::default();
|
|
let index = InMemoryIndex::default();
|
|
// TODO(konstin): Should we also use the bootstrapped pythons here?
|
|
let real_interpreter =
|
|
find_default_python(&Cache::temp().unwrap()).expect("Expected a python to be installed");
|
|
let interpreter = Interpreter::artificial(real_interpreter.platform().clone(), markers.clone());
|
|
let build_context = DummyContext::new(Cache::temp()?, interpreter.clone());
|
|
let hashes = HashStrategy::None;
|
|
let installed_packages = EmptyInstalledPackages;
|
|
let resolver = Resolver::new(
|
|
manifest,
|
|
options,
|
|
markers,
|
|
&interpreter,
|
|
tags,
|
|
&client,
|
|
&flat_index,
|
|
&index,
|
|
&hashes,
|
|
&build_context,
|
|
&installed_packages,
|
|
)?;
|
|
Ok(resolver.resolve().await?)
|
|
}
|
|
|
|
macro_rules! assert_snapshot {
|
|
($value:expr, @$snapshot:literal) => {
|
|
let snapshot = anstream::adapter::strip_str(&format!("{}", $value)).to_string();
|
|
insta::assert_snapshot!(&snapshot, @$snapshot)
|
|
};
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black<=23.9.1").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_colorama() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black[colorama]<=23.9.1").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
colorama==0.4.6
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Resolve Black with an invalid extra. The resolver should ignore the extra.
|
|
#[tokio::test]
|
|
async fn black_tensorboard() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black[tensorboard]<=23.9.1").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_python_310() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black<=23.9.1").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_310, &TAGS_310).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
tomli==2.0.1
|
|
# via black
|
|
typing-extensions==4.8.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Resolve `black` with a constraint on `mypy-extensions`, to ensure that constraints are
|
|
/// respected.
|
|
#[tokio::test]
|
|
async fn black_mypy_extensions() -> Result<()> {
|
|
let manifest = Manifest::new(
|
|
vec![
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1").unwrap())
|
|
.unwrap(),
|
|
],
|
|
Constraints::from_requirements(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("mypy-extensions<0.4.4").unwrap(),
|
|
)
|
|
.unwrap()]),
|
|
Overrides::default(),
|
|
vec![],
|
|
None,
|
|
vec![],
|
|
Exclusions::default(),
|
|
vec![],
|
|
);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==0.4.3
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Resolve `black` with a constraint on `mypy-extensions[extra]`, to ensure that extras are
|
|
/// ignored when resolving constraints.
|
|
#[tokio::test]
|
|
async fn black_mypy_extensions_extra() -> Result<()> {
|
|
let manifest = Manifest::new(
|
|
vec![
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1").unwrap())
|
|
.unwrap(),
|
|
],
|
|
Constraints::from_requirements(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("mypy-extensions[extra]<0.4.4").unwrap(),
|
|
)
|
|
.unwrap()]),
|
|
Overrides::default(),
|
|
vec![],
|
|
None,
|
|
vec![],
|
|
Exclusions::default(),
|
|
vec![],
|
|
);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==0.4.3
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Resolve `black` with a redundant constraint on `flake8`, to ensure that constraints don't
|
|
/// introduce new dependencies.
|
|
#[tokio::test]
|
|
async fn black_flake8() -> Result<()> {
|
|
let manifest = Manifest::new(
|
|
vec![
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1").unwrap())
|
|
.unwrap(),
|
|
],
|
|
Constraints::from_requirements(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("flake8<1").unwrap(),
|
|
)
|
|
.unwrap()]),
|
|
Overrides::default(),
|
|
vec![],
|
|
None,
|
|
vec![],
|
|
Exclusions::default(),
|
|
vec![],
|
|
);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_lowest() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black>21").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.resolution_mode(ResolutionMode::Lowest)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==22.1.0
|
|
click==8.0.0
|
|
# via black
|
|
mypy-extensions==0.4.3
|
|
# via black
|
|
pathspec==0.9.0
|
|
# via black
|
|
platformdirs==2.0.0
|
|
# via black
|
|
tomli==1.1.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_lowest_direct() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black>21").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.resolution_mode(ResolutionMode::LowestDirect)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==22.1.0
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
tomli==2.0.1
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_respect_preference() -> Result<()> {
|
|
let manifest = Manifest::new(
|
|
vec![Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1")?).unwrap()],
|
|
Constraints::default(),
|
|
Overrides::default(),
|
|
vec![Preference::from_requirement(
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("black==23.9.0")?).unwrap(),
|
|
)],
|
|
None,
|
|
vec![],
|
|
Exclusions::default(),
|
|
vec![],
|
|
);
|
|
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.0
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_ignore_preference() -> Result<()> {
|
|
let manifest = Manifest::new(
|
|
vec![Requirement::from_pep508(pep508_rs::Requirement::from_str("black<=23.9.1")?).unwrap()],
|
|
Constraints::default(),
|
|
Overrides::default(),
|
|
vec![Preference::from_requirement(
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("black==23.9.2")?).unwrap(),
|
|
)],
|
|
None,
|
|
vec![],
|
|
Exclusions::default(),
|
|
vec![],
|
|
);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
black==23.9.1
|
|
click==8.1.7
|
|
# via black
|
|
mypy-extensions==1.0.0
|
|
# via black
|
|
packaging==23.2
|
|
# via black
|
|
pathspec==0.11.2
|
|
# via black
|
|
platformdirs==4.0.0
|
|
# via black
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_disallow_prerelease() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black<=20.0").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.prerelease_mode(PreReleaseMode::Disallow)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let err = resolve(manifest, options, &MARKERS_311, &TAGS_311)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert_snapshot!(err, @r###"
|
|
Because only black>20.0 is available and you require black<=20.0, we can conclude that the requirements are unsatisfiable.
|
|
|
|
hint: Pre-releases are available for black in the requested range (e.g., 19.10b0), but pre-releases weren't enabled (try: `--prerelease=allow`)
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn black_allow_prerelease_if_necessary() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("black<=20.0").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.prerelease_mode(PreReleaseMode::IfNecessary)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let err = resolve(manifest, options, &MARKERS_311, &TAGS_311)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert_snapshot!(err, @r###"
|
|
Because only black>20.0 is available and you require black<=20.0, we can conclude that the requirements are unsatisfiable.
|
|
|
|
hint: Pre-releases are available for black in the requested range (e.g., 19.10b0), but pre-releases weren't enabled (try: `--prerelease=allow`)
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn pylint_disallow_prerelease() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.prerelease_mode(PreReleaseMode::Disallow)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
astroid==3.0.1
|
|
# via pylint
|
|
isort==5.12.0
|
|
# via pylint
|
|
mccabe==0.7.0
|
|
# via pylint
|
|
pylint==2.3.0
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn pylint_allow_prerelease() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.prerelease_mode(PreReleaseMode::Allow)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
astroid==3.0.1
|
|
# via pylint
|
|
isort==6.0.0b2
|
|
# via pylint
|
|
mccabe==0.7.0
|
|
# via pylint
|
|
pylint==2.3.0
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn pylint_allow_explicit_prerelease_without_marker() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap())
|
|
.unwrap(),
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("isort>=5.0.0").unwrap())
|
|
.unwrap(),
|
|
]);
|
|
let options = OptionsBuilder::new()
|
|
.prerelease_mode(PreReleaseMode::Explicit)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
astroid==3.0.1
|
|
# via pylint
|
|
isort==5.12.0
|
|
# via pylint
|
|
mccabe==0.7.0
|
|
# via pylint
|
|
pylint==2.3.0
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn pylint_allow_explicit_prerelease_with_marker() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("pylint==2.3.0").unwrap())
|
|
.unwrap(),
|
|
Requirement::from_pep508(pep508_rs::Requirement::from_str("isort>=5.0.0b").unwrap())
|
|
.unwrap(),
|
|
]);
|
|
let options = OptionsBuilder::new()
|
|
.prerelease_mode(PreReleaseMode::Explicit)
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let resolution = resolve(manifest, options, &MARKERS_311, &TAGS_311).await?;
|
|
|
|
assert_snapshot!(DisplayResolutionGraph::from(&resolution), @r###"
|
|
astroid==3.0.1
|
|
# via pylint
|
|
isort==6.0.0b2
|
|
# via pylint
|
|
mccabe==0.7.0
|
|
# via pylint
|
|
pylint==2.3.0
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Resolve `msgraph-sdk==1.0.0`, which depends on `msgraph-core>=1.0.0a2`. The resolver should
|
|
/// fail with a pre-release-centric hint.
|
|
#[tokio::test]
|
|
async fn msgraph_sdk() -> Result<()> {
|
|
let manifest = Manifest::simple(vec![Requirement::from_pep508(
|
|
pep508_rs::Requirement::from_str("msgraph-sdk==1.0.0").unwrap(),
|
|
)
|
|
.unwrap()]);
|
|
let options = OptionsBuilder::new()
|
|
.exclude_newer(Some(*EXCLUDE_NEWER))
|
|
.build();
|
|
|
|
let err = resolve(manifest, options, &MARKERS_311, &TAGS_311)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
assert_snapshot!(err, @r###"
|
|
Because only msgraph-core<1.0.0a2 is available and msgraph-sdk==1.0.0 depends on msgraph-core>=1.0.0a2, we can conclude that msgraph-sdk==1.0.0 cannot be used.
|
|
And because you require msgraph-sdk==1.0.0, we can conclude that the requirements are unsatisfiable.
|
|
|
|
hint: msgraph-core was requested with a pre-release marker (e.g., msgraph-core>=1.0.0a2), but pre-releases weren't enabled (try: `--prerelease=allow`)
|
|
"###);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
static MARKERS_311: Lazy<MarkerEnvironment> = Lazy::new(|| {
|
|
MarkerEnvironment {
|
|
implementation_name: "cpython".to_string(),
|
|
implementation_version: StringVersion::from_str("3.11.5").unwrap(),
|
|
os_name: "posix".to_string(),
|
|
platform_machine: "arm64".to_string(),
|
|
platform_python_implementation: "CPython".to_string(),
|
|
platform_release: "21.6.0".to_string(),
|
|
platform_system: "Darwin".to_string(),
|
|
platform_version: "Darwin Kernel Version 21.6.0: Mon Aug 22 20:19:52 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T6000".to_string(),
|
|
python_full_version: StringVersion::from_str("3.11.5").unwrap(),
|
|
python_version: StringVersion::from_str("3.11").unwrap(),
|
|
sys_platform: "darwin".to_string(),
|
|
}
|
|
});
|
|
|
|
static TAGS_311: Lazy<Tags> = Lazy::new(|| {
|
|
Tags::from_env(
|
|
&Platform::new(
|
|
Os::Macos {
|
|
major: 21,
|
|
minor: 6,
|
|
},
|
|
Arch::Aarch64,
|
|
),
|
|
(3, 11),
|
|
"cpython",
|
|
(3, 11),
|
|
false,
|
|
)
|
|
.unwrap()
|
|
});
|
|
|
|
static MARKERS_310: Lazy<MarkerEnvironment> = Lazy::new(|| {
|
|
MarkerEnvironment {
|
|
implementation_name: "cpython".to_string(),
|
|
implementation_version: StringVersion::from_str("3.10.5").unwrap(),
|
|
os_name: "posix".to_string(),
|
|
platform_machine: "arm64".to_string(),
|
|
platform_python_implementation: "CPython".to_string(),
|
|
platform_release: "21.6.0".to_string(),
|
|
platform_system: "Darwin".to_string(),
|
|
platform_version: "Darwin Kernel Version 21.6.0: Mon Aug 22 20:19:52 PDT 2022; root:xnu-8020.140.49~2/RELEASE_ARM64_T6000".to_string(),
|
|
python_full_version: StringVersion::from_str("3.10.5").unwrap(),
|
|
python_version: StringVersion::from_str("3.10").unwrap(),
|
|
sys_platform: "darwin".to_string(),
|
|
}
|
|
});
|
|
|
|
static TAGS_310: Lazy<Tags> = Lazy::new(|| {
|
|
Tags::from_env(
|
|
&Platform::new(
|
|
Os::Macos {
|
|
major: 21,
|
|
minor: 6,
|
|
},
|
|
Arch::Aarch64,
|
|
),
|
|
(3, 10),
|
|
"cpython",
|
|
(3, 10),
|
|
false,
|
|
)
|
|
.unwrap()
|
|
});
|