mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-01 14:31:12 +00:00
Tweak some of the tool.uv.sources
error messages for consistency (#3364)
This commit is contained in:
parent
100935f4f1
commit
363e808724
3 changed files with 47 additions and 43 deletions
|
@ -146,9 +146,9 @@ pub enum RequirementSource {
|
||||||
/// `<scheme>://<domain>/<path>#subdirectory=<subdirectory>`.
|
/// `<scheme>://<domain>/<path>#subdirectory=<subdirectory>`.
|
||||||
url: VerbatimUrl,
|
url: VerbatimUrl,
|
||||||
},
|
},
|
||||||
/// A remote git repository, either over HTTPS or over SSH.
|
/// A remote Git repository, over either HTTPS or SSH.
|
||||||
Git {
|
Git {
|
||||||
/// The repository URL (without `git+` prefix).
|
/// The repository URL (without the `git+` prefix).
|
||||||
repository: Url,
|
repository: Url,
|
||||||
/// Optionally, the revision, tag, or branch to use.
|
/// Optionally, the revision, tag, or branch to use.
|
||||||
reference: GitReference,
|
reference: GitReference,
|
||||||
|
|
|
@ -31,7 +31,7 @@ use crate::ExtrasSpecification;
|
||||||
pub enum Pep621Error {
|
pub enum Pep621Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Pep508(#[from] pep508_rs::Pep508Error),
|
Pep508(#[from] pep508_rs::Pep508Error),
|
||||||
#[error("You need to specify a `[project]` section to use `[tool.uv.sources]`")]
|
#[error("Must specify a `[project]` section alongside `[tool.uv.sources]`")]
|
||||||
MissingProjectSection,
|
MissingProjectSection,
|
||||||
#[error("pyproject.toml section is declared as dynamic, but must be static: `{0}`")]
|
#[error("pyproject.toml section is declared as dynamic, but must be static: `{0}`")]
|
||||||
CantBeDynamic(&'static str),
|
CantBeDynamic(&'static str),
|
||||||
|
@ -47,32 +47,33 @@ pub enum LoweringError {
|
||||||
DirectUrl(#[from] Box<ParsedUrlError>),
|
DirectUrl(#[from] Box<ParsedUrlError>),
|
||||||
#[error("Unsupported path (can't convert to URL): `{}`", _0.user_display())]
|
#[error("Unsupported path (can't convert to URL): `{}`", _0.user_display())]
|
||||||
PathToUrl(PathBuf),
|
PathToUrl(PathBuf),
|
||||||
#[error("The package is not included as workspace package in `tool.uv.workspace`")]
|
#[error("Package is not included as workspace package in `tool.uv.workspace`")]
|
||||||
UndeclaredWorkspacePackage,
|
UndeclaredWorkspacePackage,
|
||||||
#[error("You need to specify a version constraint")]
|
#[error("Must specify a version constraint")]
|
||||||
UnconstrainedVersion,
|
UnconstrainedVersion,
|
||||||
#[error("You can only use one of rev, tag or branch")]
|
#[error("Can only specify one of rev, tag, or branch")]
|
||||||
MoreThanOneGitRef,
|
MoreThanOneGitRef,
|
||||||
#[error("You can't combine these options in `tool.uv.sources`")]
|
#[error("Unable to combine options in `tool.uv.sources`")]
|
||||||
InvalidEntry,
|
InvalidEntry,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InvalidUrl(#[from] url::ParseError),
|
InvalidUrl(#[from] url::ParseError),
|
||||||
#[error("You can't combine a url in `project` with `tool.uv.sources`")]
|
#[error("Can't combine URLs from both `project.dependencies` and `tool.uv.sources`")]
|
||||||
ConflictingUrls,
|
ConflictingUrls,
|
||||||
/// Note: Infallible on unix and windows.
|
|
||||||
#[error("Could not normalize path: `{0}`")]
|
#[error("Could not normalize path: `{0}`")]
|
||||||
AbsolutizeError(String, #[source] io::Error),
|
AbsolutizeError(String, #[source] io::Error),
|
||||||
#[error("Fragments are not allowed in URLs: `{0}`")]
|
#[error("Fragments are not allowed in URLs: `{0}`")]
|
||||||
ForbiddenFragment(Url),
|
ForbiddenFragment(Url),
|
||||||
|
#[error("`workspace = false` is not yet supported")]
|
||||||
|
WorkspaceFalse,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `pyproject.toml` as specified in PEP 517.
|
/// A `pyproject.toml` as specified in PEP 517.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct PyProjectToml {
|
pub struct PyProjectToml {
|
||||||
/// Project metadata
|
/// PEP 621-compliant project metadata.
|
||||||
pub project: Option<Project>,
|
pub project: Option<Project>,
|
||||||
/// Uv additions
|
/// Proprietary additions.
|
||||||
pub tool: Option<Tool>,
|
pub tool: Option<Tool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,17 +150,18 @@ impl Deref for SerdePattern {
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(untagged, deny_unknown_fields)]
|
#[serde(untagged, deny_unknown_fields)]
|
||||||
pub enum Source {
|
pub enum Source {
|
||||||
/// A remote git repository, either over HTTPS or over SSH.
|
/// A remote Git repository, available over HTTPS or SSH.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```toml
|
/// ```toml
|
||||||
/// flask = { git = "https://github.com/pallets/flask", tag = "3.0.0" }
|
/// flask = { git = "https://github.com/pallets/flask", tag = "3.0.0" }
|
||||||
/// ```
|
/// ```
|
||||||
Git {
|
Git {
|
||||||
|
/// The repository URL (without the `git+` prefix).
|
||||||
git: Url,
|
git: Url,
|
||||||
/// The path to the directory with the `pyproject.toml` if it is not in the archive root.
|
/// The path to the directory with the `pyproject.toml`, if it's not in the archive root.
|
||||||
subdirectory: Option<String>,
|
subdirectory: Option<String>,
|
||||||
// Only one of the three may be used, we validate this later for a better error message.
|
// Only one of the three may be used; we'll validate this later and emit a custom error.
|
||||||
rev: Option<String>,
|
rev: Option<String>,
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
branch: Option<String>,
|
branch: Option<String>,
|
||||||
|
@ -173,33 +175,32 @@ pub enum Source {
|
||||||
/// ```
|
/// ```
|
||||||
Url {
|
Url {
|
||||||
url: Url,
|
url: Url,
|
||||||
/// For source distributions, the path to the directory with the `pyproject.toml` if it is
|
/// For source distributions, the path to the directory with the `pyproject.toml`, if it's
|
||||||
/// not in the archive root.
|
/// not in the archive root.
|
||||||
subdirectory: Option<String>,
|
subdirectory: Option<String>,
|
||||||
},
|
},
|
||||||
/// The path to a dependency. It can either be a wheel (a `.whl` file), a source distribution
|
/// The path to a dependency, either a wheel (a `.whl` file), source distribution (a `.zip` or
|
||||||
/// as archive (a `.zip` or `.tag.gz` file) or a source distribution as directory (a directory
|
/// `.tag.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or
|
||||||
/// with a pyproject.toml in, or a legacy directory with only a setup.py but non pyproject.toml
|
/// `setup.py` file in the root).
|
||||||
/// in it).
|
|
||||||
Path {
|
Path {
|
||||||
path: String,
|
path: String,
|
||||||
/// `false` by default.
|
/// `false` by default.
|
||||||
editable: Option<bool>,
|
editable: Option<bool>,
|
||||||
},
|
},
|
||||||
/// When using a version as requirement, you can optionally pin the requirement to an index
|
/// A dependency pinned to a specific index, e.g., `torch` after setting `torch` to `https://download.pytorch.org/whl/cu118`.
|
||||||
/// you defined, e.g. `torch` after configuring `torch` to
|
|
||||||
/// `https://download.pytorch.org/whl/cu118`.
|
|
||||||
Registry {
|
Registry {
|
||||||
// TODO(konstin): The string is more-or-less a placeholder
|
// TODO(konstin): The string is more-or-less a placeholder
|
||||||
index: String,
|
index: String,
|
||||||
},
|
},
|
||||||
/// A dependency on another package in the workspace.
|
/// A dependency on another package in the workspace.
|
||||||
Workspace {
|
Workspace {
|
||||||
|
/// When set to `false`, the package will be fetched from the remote index, rather than
|
||||||
|
/// included as a workspace package.
|
||||||
workspace: bool,
|
workspace: bool,
|
||||||
/// `true` by default.
|
/// `true` by default.
|
||||||
editable: Option<bool>,
|
editable: Option<bool>,
|
||||||
},
|
},
|
||||||
/// Show a better error message for invalid combinations of options.
|
/// A catch-all variant used to emit precise error messages when deserializing.
|
||||||
CatchAll {
|
CatchAll {
|
||||||
git: String,
|
git: String,
|
||||||
subdirectory: Option<String>,
|
subdirectory: Option<String>,
|
||||||
|
@ -365,7 +366,7 @@ pub(crate) fn lower_requirements(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combine `project.dependencies`/`project.optional-dependencies` with `tool.uv.sources`.
|
/// Combine `project.dependencies` or `project.optional-dependencies` with `tool.uv.sources`.
|
||||||
pub(crate) fn lower_requirement(
|
pub(crate) fn lower_requirement(
|
||||||
requirement: pep508_rs::Requirement,
|
requirement: pep508_rs::Requirement,
|
||||||
project_name: &PackageName,
|
project_name: &PackageName,
|
||||||
|
@ -391,7 +392,8 @@ pub(crate) fn lower_requirement(
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(source) = source else {
|
let Some(source) = source else {
|
||||||
// Support recursive editable inclusions. TODO(konsti): This is a workspace feature.
|
// Support recursive editable inclusions.
|
||||||
|
// TODO(konsti): This is a workspace feature.
|
||||||
return if requirement.version_or_url.is_none() && &requirement.name != project_name {
|
return if requirement.version_or_url.is_none() && &requirement.name != project_name {
|
||||||
Err(LoweringError::UnconstrainedVersion)
|
Err(LoweringError::UnconstrainedVersion)
|
||||||
} else {
|
} else {
|
||||||
|
@ -482,12 +484,12 @@ pub(crate) fn lower_requirement(
|
||||||
workspace,
|
workspace,
|
||||||
editable,
|
editable,
|
||||||
} => {
|
} => {
|
||||||
|
if !workspace {
|
||||||
|
return Err(LoweringError::WorkspaceFalse);
|
||||||
|
}
|
||||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||||
return Err(LoweringError::ConflictingUrls);
|
return Err(LoweringError::ConflictingUrls);
|
||||||
}
|
}
|
||||||
if !workspace {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
let path = workspace_packages
|
let path = workspace_packages
|
||||||
.get(&requirement.name)
|
.get(&requirement.name)
|
||||||
.ok_or(LoweringError::UndeclaredWorkspacePackage)?
|
.ok_or(LoweringError::UndeclaredWorkspacePackage)?
|
||||||
|
@ -495,7 +497,7 @@ pub(crate) fn lower_requirement(
|
||||||
path_source(path, project_dir, editable)?
|
path_source(path, project_dir, editable)?
|
||||||
}
|
}
|
||||||
Source::CatchAll { .. } => {
|
Source::CatchAll { .. } => {
|
||||||
// This is better than a serde error about not matching any enum variant
|
// Emit a dedicated error message, which is an improvement over Serde's default error.
|
||||||
return Err(LoweringError::InvalidEntry);
|
return Err(LoweringError::InvalidEntry);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -526,8 +528,8 @@ fn path_source(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given an extra in a project that may contain references to the project
|
/// Given an extra in a project that may contain references to the project itself, flatten it into
|
||||||
/// itself, flatten it into a list of requirements.
|
/// a list of requirements.
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
/// ```toml
|
/// ```toml
|
||||||
|
@ -665,7 +667,7 @@ mod test {
|
||||||
assert_snapshot!(format_err(input), @r###"
|
assert_snapshot!(format_err(input), @r###"
|
||||||
error: Failed to parse `pyproject.toml`
|
error: Failed to parse `pyproject.toml`
|
||||||
Caused by: Failed to parse entry for: `tqdm`
|
Caused by: Failed to parse entry for: `tqdm`
|
||||||
Caused by: You can't combine a url in `project` with `tool.uv.sources`
|
Caused by: Can't combine URLs from both `project.dependencies` and `tool.uv.sources`
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -686,7 +688,7 @@ mod test {
|
||||||
assert_snapshot!(format_err(input), @r###"
|
assert_snapshot!(format_err(input), @r###"
|
||||||
error: Failed to parse `pyproject.toml`
|
error: Failed to parse `pyproject.toml`
|
||||||
Caused by: Failed to parse entry for: `tqdm`
|
Caused by: Failed to parse entry for: `tqdm`
|
||||||
Caused by: You can only use one of rev, tag or branch
|
Caused by: Can only specify one of rev, tag, or branch
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -756,7 +758,7 @@ mod test {
|
||||||
assert_snapshot!(format_err(input), @r###"
|
assert_snapshot!(format_err(input), @r###"
|
||||||
error: Failed to parse `pyproject.toml`
|
error: Failed to parse `pyproject.toml`
|
||||||
Caused by: Failed to parse entry for: `tqdm`
|
Caused by: Failed to parse entry for: `tqdm`
|
||||||
Caused by: You need to specify a version constraint
|
Caused by: Must specify a version constraint
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -828,7 +830,7 @@ mod test {
|
||||||
assert_snapshot!(format_err(input), @r###"
|
assert_snapshot!(format_err(input), @r###"
|
||||||
error: Failed to parse `pyproject.toml`
|
error: Failed to parse `pyproject.toml`
|
||||||
Caused by: Failed to parse entry for: `tqdm`
|
Caused by: Failed to parse entry for: `tqdm`
|
||||||
Caused by: You can't combine a url in `project` with `tool.uv.sources`
|
Caused by: Can't combine URLs from both `project.dependencies` and `tool.uv.sources`
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -849,7 +851,7 @@ mod test {
|
||||||
assert_snapshot!(format_err(input), @r###"
|
assert_snapshot!(format_err(input), @r###"
|
||||||
error: Failed to parse `pyproject.toml`
|
error: Failed to parse `pyproject.toml`
|
||||||
Caused by: Failed to parse entry for: `tqdm`
|
Caused by: Failed to parse entry for: `tqdm`
|
||||||
Caused by: The package is not included as workspace package in `tool.uv.workspace`
|
Caused by: Package is not included as workspace package in `tool.uv.workspace`
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,7 +884,7 @@ mod test {
|
||||||
|
|
||||||
assert_snapshot!(format_err(input), @r###"
|
assert_snapshot!(format_err(input), @r###"
|
||||||
error: Failed to parse `pyproject.toml`
|
error: Failed to parse `pyproject.toml`
|
||||||
Caused by: You need to specify a `[project]` section to use `[tool.uv.sources]`
|
Caused by: Must specify a `[project]` section alongside `[tool.uv.sources]`
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
uv.schema.json
generated
14
uv.schema.json
generated
|
@ -596,7 +596,7 @@
|
||||||
"description": "A `tool.uv.sources` value.",
|
"description": "A `tool.uv.sources` value.",
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"description": "A remote git repository, either over HTTPS or over SSH.\n\nExample: ```toml flask = { git = \"https://github.com/pallets/flask\", tag = \"3.0.0\" } ```",
|
"description": "A remote Git repository, available over HTTPS or SSH.\n\nExample: ```toml flask = { git = \"https://github.com/pallets/flask\", tag = \"3.0.0\" } ```",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"git"
|
"git"
|
||||||
|
@ -609,6 +609,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
|
"description": "The repository URL (without the `git+` prefix).",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uri"
|
"format": "uri"
|
||||||
},
|
},
|
||||||
|
@ -619,7 +620,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"subdirectory": {
|
"subdirectory": {
|
||||||
"description": "The path to the directory with the `pyproject.toml` if it is not in the archive root.",
|
"description": "The path to the directory with the `pyproject.toml`, if it's not in the archive root.",
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
|
@ -642,7 +643,7 @@
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"subdirectory": {
|
"subdirectory": {
|
||||||
"description": "For source distributions, the path to the directory with the `pyproject.toml` if it is not in the archive root.",
|
"description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's not in the archive root.",
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
"null"
|
"null"
|
||||||
|
@ -656,7 +657,7 @@
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "The path to a dependency. It can either be a wheel (a `.whl` file), a source distribution as archive (a `.zip` or `.tag.gz` file) or a source distribution as directory (a directory with a pyproject.toml in, or a legacy directory with only a setup.py but non pyproject.toml in it).",
|
"description": "The path to a dependency, either a wheel (a `.whl` file), source distribution (a `.zip` or `.tag.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or `setup.py` file in the root).",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"path"
|
"path"
|
||||||
|
@ -676,7 +677,7 @@
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "When using a version as requirement, you can optionally pin the requirement to an index you defined, e.g. `torch` after configuring `torch` to `https://download.pytorch.org/whl/cu118`.",
|
"description": "A dependency pinned to a specific index, e.g., `torch` after setting `torch` to `https://download.pytorch.org/whl/cu118`.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"index"
|
"index"
|
||||||
|
@ -703,13 +704,14 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
|
"description": "When set to `false`, the package will be fetched from the remote index, rather than included as a workspace package.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Show a better error message for invalid combinations of options.",
|
"description": "A catch-all variant used to emit precise error messages when deserializing.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"git",
|
"git",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue