Autogenerate possible values for enums in reference documentation (#5137)

## Summary

For example:

![Screenshot 2024-07-16 at 7 44
10 PM](https://github.com/user-attachments/assets/73ce16ba-eb0e-43c4-a741-65a54637452f)

Closes https://github.com/astral-sh/uv/issues/5129.
This commit is contained in:
Charlie Marsh 2024-07-17 12:37:33 -04:00 committed by GitHub
parent 3e93255ac9
commit a191f84929
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 218 additions and 62 deletions

1
Cargo.lock generated
View file

@ -5070,6 +5070,7 @@ dependencies = [
name = "uv-settings" name = "uv-settings"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"clap",
"dirs-sys", "dirs-sys",
"distribution-types", "distribution-types",
"fs-err", "fs-err",

View file

@ -216,7 +216,18 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
output.push_str("\n\n"); output.push_str("\n\n");
output.push_str(&format!("**Default value**: `{}`\n", field.default)); output.push_str(&format!("**Default value**: `{}`\n", field.default));
output.push('\n'); output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type)); if let Some(possible_values) = field
.possible_values
.as_ref()
.filter(|values| !values.is_empty())
{
output.push_str("**Possible values**:\n\n");
for value in possible_values {
output.push_str(format!("- {value}\n").as_str());
}
} else {
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
}
output.push('\n'); output.push('\n');
output.push_str("**Example usage**:\n\n"); output.push_str("**Example usage**:\n\n");
output.push_str(&format_tab( output.push_str(&format_tab(

View file

@ -8,7 +8,7 @@ use syn::meta::ParseNestedMeta;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{ use syn::{
AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, ExprLit, Field, AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, ExprLit, Field,
Fields, Lit, LitStr, Meta, Path, PathArguments, PathSegment, Type, TypePath, Fields, GenericArgument, Lit, LitStr, Meta, Path, PathArguments, PathSegment, Type, TypePath,
}; };
use textwrap::dedent; use textwrap::dedent;
@ -194,6 +194,7 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
value_type, value_type,
example, example,
scope, scope,
possible_values,
} = parse_field_attributes(attr)?; } = parse_field_attributes(attr)?;
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span()); let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
@ -224,6 +225,25 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
quote!(None) quote!(None)
}; };
let possible_values = if possible_values == Some(true) {
let inner_type = get_inner_type_if_option(&field.ty).unwrap_or(&field.ty);
let inner_type = quote!(#inner_type);
quote!(
Some(
<#inner_type as clap::ValueEnum>::value_variants()
.iter()
.filter_map(clap::ValueEnum::to_possible_value)
.map(|value| uv_options_metadata::PossibleValue {
name: value.get_name().to_string(),
help: value.get_help().map(ToString::to_string),
})
.collect()
)
)
} else {
quote!(None)
};
Ok(quote_spanned!( Ok(quote_spanned!(
ident.span() => { ident.span() => {
visit.record_field(#kebab_name, uv_options_metadata::OptionField{ visit.record_field(#kebab_name, uv_options_metadata::OptionField{
@ -232,7 +252,8 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
value_type: &#value_type, value_type: &#value_type,
example: &#example, example: &#example,
scope: #scope, scope: #scope,
deprecated: #deprecated deprecated: #deprecated,
possible_values: #possible_values,
}) })
} }
)) ))
@ -244,6 +265,7 @@ struct FieldAttributes {
value_type: String, value_type: String,
example: String, example: String,
scope: Option<String>, scope: Option<String>,
possible_values: Option<bool>,
} }
fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes> { fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes> {
@ -251,6 +273,7 @@ fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes>
let mut value_type = None; let mut value_type = None;
let mut example = None; let mut example = None;
let mut scope = None; let mut scope = None;
let mut possible_values = None;
attribute.parse_nested_meta(|meta| { attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("default") { if meta.path.is_ident("default") {
@ -262,6 +285,8 @@ fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes>
} else if meta.path.is_ident("example") { } else if meta.path.is_ident("example") {
let example_text = get_string_literal(&meta, "value_type", "option")?.value(); let example_text = get_string_literal(&meta, "value_type", "option")?.value();
example = Some(dedent(&example_text).trim_matches('\n').to_string()); example = Some(dedent(&example_text).trim_matches('\n').to_string());
} else if meta.path.is_ident("possible_values") {
possible_values = get_bool_literal(&meta, "possible_values", "option")?;
} else { } else {
return Err(syn::Error::new( return Err(syn::Error::new(
meta.path.span(), meta.path.span(),
@ -292,6 +317,7 @@ fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes>
value_type, value_type,
example, example,
scope, scope,
possible_values,
}) })
} }
@ -318,6 +344,23 @@ fn parse_deprecated_attribute(attribute: &Attribute) -> syn::Result<DeprecatedAt
Ok(deprecated) Ok(deprecated)
} }
fn get_inner_type_if_option(ty: &Type) -> Option<&Type> {
if let Type::Path(type_path) = ty {
if type_path.path.segments.len() == 1 && type_path.path.segments[0].ident == "Option" {
if let PathArguments::AngleBracketed(angle_bracketed_args) =
&type_path.path.segments[0].arguments
{
if angle_bracketed_args.args.len() == 1 {
if let GenericArgument::Type(inner_type) = &angle_bracketed_args.args[0] {
return Some(inner_type);
}
}
}
}
}
None
}
fn get_string_literal( fn get_string_literal(
meta: &ParseNestedMeta, meta: &ParseNestedMeta,
meta_name: &str, meta_name: &str,
@ -351,6 +394,32 @@ fn get_string_literal(
} }
} }
fn get_bool_literal(
meta: &ParseNestedMeta,
meta_name: &str,
attribute_name: &str,
) -> syn::Result<Option<bool>> {
let expr: syn::Expr = meta.value()?.parse()?;
let mut value = &expr;
while let syn::Expr::Group(e) = value {
value = &e.expr;
}
if let syn::Expr::Lit(ExprLit {
lit: Lit::Bool(lit),
..
}) = value
{
Ok(Some(lit.value))
} else {
Err(syn::Error::new(
expr.span(),
format!("expected {attribute_name} attribute to be a boolean: `{meta_name} = true`"),
))
}
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct DeprecatedAttribute { struct DeprecatedAttribute {
since: Option<String>, since: Option<String>,

View file

@ -119,6 +119,7 @@ impl OptionSet {
/// example: "", /// example: "",
/// scope: None, /// scope: None,
/// deprecated: None, /// deprecated: None,
/// possible_values: None
/// }); /// });
/// } /// }
/// } /// }
@ -141,7 +142,8 @@ impl OptionSet {
/// value_type: "bool", /// value_type: "bool",
/// example: "", /// example: "",
/// scope: None, /// scope: None,
/// deprecated: None /// deprecated: None,
/// possible_values: None
/// }); /// });
/// ///
/// visit.record_set("format", Nested::metadata()); /// visit.record_set("format", Nested::metadata());
@ -158,7 +160,8 @@ impl OptionSet {
/// value_type: "bool", /// value_type: "bool",
/// example: "", /// example: "",
/// scope: None, /// scope: None,
/// deprecated: None /// deprecated: None,
/// possible_values: None
/// }); /// });
/// } /// }
/// } /// }
@ -190,7 +193,8 @@ impl OptionSet {
/// value_type: "bool", /// value_type: "bool",
/// example: "", /// example: "",
/// scope: None, /// scope: None,
/// deprecated: None /// deprecated: None,
/// possible_values: None
/// }; /// };
/// ///
/// impl OptionsMetadata for WithOptions { /// impl OptionsMetadata for WithOptions {
@ -213,7 +217,8 @@ impl OptionSet {
/// value_type: "bool", /// value_type: "bool",
/// example: "", /// example: "",
/// scope: None, /// scope: None,
/// deprecated: None /// deprecated: None,
/// possible_values: None
/// }; /// };
/// ///
/// struct Root; /// struct Root;
@ -226,7 +231,8 @@ impl OptionSet {
/// value_type: "bool", /// value_type: "bool",
/// example: "", /// example: "",
/// scope: None, /// scope: None,
/// deprecated: None /// deprecated: None,
/// possible_values: None
/// }); /// });
/// ///
/// visit.record_set("format", Nested::metadata()); /// visit.record_set("format", Nested::metadata());
@ -388,6 +394,7 @@ pub struct OptionField {
pub scope: Option<&'static str>, pub scope: Option<&'static str>,
pub example: &'static str, pub example: &'static str,
pub deprecated: Option<Deprecated>, pub deprecated: Option<Deprecated>,
pub possible_values: Option<Vec<PossibleValue>>,
} }
#[derive(Debug, Clone, Eq, PartialEq, Serialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize)]
@ -400,8 +407,22 @@ impl Display for OptionField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.doc)?; writeln!(f, "{}", self.doc)?;
writeln!(f)?; writeln!(f)?;
writeln!(f, "Default value: {}", self.default)?; writeln!(f, "Default value: {}", self.default)?;
writeln!(f, "Type: {}", self.value_type)?;
if let Some(possible_values) = self
.possible_values
.as_ref()
.filter(|values| !values.is_empty())
{
writeln!(f, "Possible values:")?;
writeln!(f)?;
for value in possible_values {
writeln!(f, "- {value}")?;
}
} else {
writeln!(f, "Type: {}", self.value_type)?;
}
if let Some(deprecated) = &self.deprecated { if let Some(deprecated) = &self.deprecated {
write!(f, "Deprecated")?; write!(f, "Deprecated")?;
@ -420,3 +441,21 @@ impl Display for OptionField {
writeln!(f, "Example usage:\n```toml\n{}\n```", self.example) writeln!(f, "Example usage:\n```toml\n{}\n```", self.example)
} }
} }
/// A possible value for an enum, similar to Clap's `PossibleValue` type (but without a dependency
/// on Clap).
#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct PossibleValue {
pub name: String,
pub help: Option<String>,
}
impl Display for PossibleValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "`\"{}\"`", self.name)?;
if let Some(help) = &self.help {
write!(f, ": {help}")?;
}
Ok(())
}
}

View file

@ -14,18 +14,19 @@ workspace = true
[dependencies] [dependencies]
distribution-types = { workspace = true, features = ["schemars"] } distribution-types = { workspace = true, features = ["schemars"] }
install-wheel-rs = { workspace = true, features = ["schemars"] } install-wheel-rs = { workspace = true, features = ["schemars", "clap"] }
pep508_rs = { workspace = true } pep508_rs = { workspace = true }
pypi-types = { workspace = true } pypi-types = { workspace = true }
uv-configuration = { workspace = true, features = ["schemars"] } uv-configuration = { workspace = true, features = ["schemars", "clap"] }
uv-fs = { workspace = true } uv-fs = { workspace = true }
uv-macros = { workspace = true } uv-macros = { workspace = true }
uv-normalize = { workspace = true, features = ["schemars"] } uv-normalize = { workspace = true, features = ["schemars"] }
uv-options-metadata = { workspace = true } uv-options-metadata = { workspace = true }
uv-python = { workspace = true, features = ["schemars"] } uv-python = { workspace = true, features = ["schemars", "clap"] }
uv-resolver = { workspace = true, features = ["schemars"] } uv-resolver = { workspace = true, features = ["schemars", "clap"] }
uv-warnings = { workspace = true } uv-warnings = { workspace = true }
clap = { workspace = true }
dirs-sys = { workspace = true } dirs-sys = { workspace = true }
fs-err = { workspace = true } fs-err = { workspace = true }
schemars = { workspace = true, optional = true } schemars = { workspace = true, optional = true }
@ -35,4 +36,4 @@ toml = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[package.metadata.cargo-shear] [package.metadata.cargo-shear]
ignored = ["uv-options-metadata"] ignored = ["uv-options-metadata", "clap"]

View file

@ -120,7 +120,8 @@ pub struct GlobalOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
python-preference = "managed" python-preference = "managed"
"# "#,
possible_values = true
)] )]
pub python_preference: Option<PythonPreference>, pub python_preference: Option<PythonPreference>,
/// Whether to automatically download Python when required. /// Whether to automatically download Python when required.
@ -129,7 +130,8 @@ pub struct GlobalOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
python-fetch = \"automatic\" python-fetch = \"automatic\"
"# "#,
possible_values = true
)] )]
pub python_fetch: Option<PythonFetch>, pub python_fetch: Option<PythonFetch>,
} }
@ -255,18 +257,13 @@ pub struct ResolverInstallerOptions {
/// limit resolutions to those present on that first index (`first-match`). This prevents /// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary. /// same name to a secondary.
///
/// Possible values:
///
/// - `"first-index"`: Only use results from the first index that returns a match for a given package name.
/// - `"unsafe-first-match"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next.
/// - `"unsafe-best-match"`: Search for every package name across all indexes, preferring the "best" version found. If a package version is in multiple indexes, only look at the entry for the first index.
#[option( #[option(
default = "\"first-index\"", default = "\"first-index\"",
value_type = "str", value_type = "str",
example = r#" example = r#"
index-strategy = "unsafe-best-match" index-strategy = "unsafe-best-match"
"# "#,
possible_values = true
)] )]
pub index_strategy: Option<IndexStrategy>, pub index_strategy: Option<IndexStrategy>,
/// Attempt to use `keyring` for authentication for index URLs. /// Attempt to use `keyring` for authentication for index URLs.
@ -290,7 +287,8 @@ pub struct ResolverInstallerOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
resolution = "lowest-direct" resolution = "lowest-direct"
"# "#,
possible_values = true
)] )]
pub resolution: Option<ResolutionMode>, pub resolution: Option<ResolutionMode>,
/// The strategy to use when considering pre-release versions. /// The strategy to use when considering pre-release versions.
@ -303,7 +301,8 @@ pub struct ResolverInstallerOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
prerelease = "allow" prerelease = "allow"
"# "#,
possible_values = true
)] )]
pub prerelease: Option<PreReleaseMode>, pub prerelease: Option<PreReleaseMode>,
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs. /// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
@ -336,7 +335,8 @@ pub struct ResolverInstallerOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
link-mode = "copy" link-mode = "copy"
"# "#,
possible_values = true
)] )]
pub link_mode: Option<LinkMode>, pub link_mode: Option<LinkMode>,
/// Compile Python files to bytecode after installation. /// Compile Python files to bytecode after installation.
@ -590,18 +590,13 @@ pub struct PipOptions {
/// limit resolutions to those present on that first index (`first-match`). This prevents /// limit resolutions to those present on that first index (`first-match`). This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the /// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary. /// same name to a secondary.
///
/// Possible values:
///
/// - `"first-index"`: Only use results from the first index that returns a match for a given package name.
/// - `"unsafe-first-match"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next.
/// - `"unsafe-best-match"`: Search for every package name across all indexes, preferring the "best" version found. If a package version is in multiple indexes, only look at the entry for the first index.
#[option( #[option(
default = "\"first-index\"", default = "\"first-index\"",
value_type = "str", value_type = "str",
example = r#" example = r#"
index-strategy = "unsafe-best-match" index-strategy = "unsafe-best-match"
"# "#,
possible_values = true
)] )]
pub index_strategy: Option<IndexStrategy>, pub index_strategy: Option<IndexStrategy>,
/// Attempt to use `keyring` for authentication for index URLs. /// Attempt to use `keyring` for authentication for index URLs.
@ -734,7 +729,8 @@ pub struct PipOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
resolution = "lowest-direct" resolution = "lowest-direct"
"# "#,
possible_values = true
)] )]
pub resolution: Option<ResolutionMode>, pub resolution: Option<ResolutionMode>,
/// The strategy to use when considering pre-release versions. /// The strategy to use when considering pre-release versions.
@ -747,7 +743,8 @@ pub struct PipOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
prerelease = "allow" prerelease = "allow"
"# "#,
possible_values = true
)] )]
pub prerelease: Option<PreReleaseMode>, pub prerelease: Option<PreReleaseMode>,
/// Write the requirements generated by `uv pip compile` to the given `requirements.txt` file. /// Write the requirements generated by `uv pip compile` to the given `requirements.txt` file.
@ -966,7 +963,8 @@ pub struct PipOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
annotation-style = "line" annotation-style = "line"
"# "#,
possible_values = true
)] )]
pub annotation_style: Option<AnnotationStyle>, pub annotation_style: Option<AnnotationStyle>,
/// The method to use when installing packages from the global cache. /// The method to use when installing packages from the global cache.
@ -978,7 +976,8 @@ pub struct PipOptions {
value_type = "str", value_type = "str",
example = r#" example = r#"
link-mode = "copy" link-mode = "copy"
"# "#,
possible_values = true
)] )]
pub link_mode: Option<LinkMode>, pub link_mode: Option<LinkMode>,
/// Compile Python files to bytecode after installation. /// Compile Python files to bytecode after installation.

View file

@ -189,15 +189,13 @@ limit resolutions to those present on that first index (`first-match`). This pre
"dependency confusion" attacks, whereby an attack can upload a malicious package under the "dependency confusion" attacks, whereby an attack can upload a malicious package under the
same name to a secondary. same name to a secondary.
Possible values:
- `"first-index"`: Only use results from the first index that returns a match for a given package name.
- `"unsafe-first-match"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next.
- `"unsafe-best-match"`: Search for every package name across all indexes, preferring the "best" version found. If a package version is in multiple indexes, only look at the entry for the first index.
**Default value**: `"first-index"` **Default value**: `"first-index"`
**Type**: `str` **Possible values**:
- `"first-index"`: Only use results from the first index that returns a match for a given package name
- `"unsafe-first-match"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next
- `"unsafe-best-match"`: Search for every package name across all indexes, preferring the "best" version found. If a package version is in multiple indexes, only look at the entry for the first index
**Example usage**: **Example usage**:
@ -284,7 +282,11 @@ Windows.
**Default value**: `"clone" (macOS) or "hardlink" (Linux, Windows)` **Default value**: `"clone" (macOS) or "hardlink" (Linux, Windows)`
**Type**: `str` **Possible values**:
- `"clone"`: Clone (i.e., copy-on-write) packages from the wheel into the site packages
- `"copy"`: Copy packages from the wheel into the site packages
- `"hardlink"`: Hard link packages from the wheel into the site packages
**Example usage**: **Example usage**:
@ -556,7 +558,13 @@ declared specifiers (`if-necessary-or-explicit`).
**Default value**: `"if-necessary-or-explicit"` **Default value**: `"if-necessary-or-explicit"`
**Type**: `str` **Possible values**:
- `"disallow"`: Disallow all pre-release versions
- `"allow"`: Allow all pre-release versions
- `"if-necessary"`: Allow pre-release versions if all versions of a package are pre-release
- `"explicit"`: Allow pre-release versions for first-party packages with explicit pre-release markers in their version requirements
- `"if-necessary-or-explicit"`: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
**Example usage**: **Example usage**:
@ -606,7 +614,10 @@ Whether to automatically download Python when required.
**Default value**: `"automatic"` **Default value**: `"automatic"`
**Type**: `str` **Possible values**:
- `"automatic"`: Automatically fetch managed Python installations when needed
- `"manual"`: Do not automatically fetch managed Python installations; require explicit installation
**Example usage**: **Example usage**:
@ -632,7 +643,13 @@ those that are downloaded and installed by uv.
**Default value**: `"installed"` **Default value**: `"installed"`
**Type**: `str` **Possible values**:
- `"only-managed"`: Only use managed Python installations; never use system Python installations
- `"installed"`: Prefer installed Python installations, only download managed Python installations if no system Python installation is found
- `"managed"`: Prefer managed Python installations over system Python installations, even if fetching is required
- `"system"`: Prefer system Python installations over managed Python installations
- `"only-system"`: Only use system Python installations; never use managed Python installations
**Example usage**: **Example usage**:
@ -710,7 +727,11 @@ By default, uv will use the latest compatible version of each package (`highest`
**Default value**: `"highest"` **Default value**: `"highest"`
**Type**: `str` **Possible values**:
- `"highest"`: Resolve the highest compatible version of each package
- `"lowest"`: Resolve the lowest compatible version of each package
- `"lowest-direct"`: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
**Example usage**: **Example usage**:
@ -852,7 +873,10 @@ source of each package.
**Default value**: `"split"` **Default value**: `"split"`
**Type**: `str` **Possible values**:
- `"line"`: Render the annotations on a single, comma-separated line
- `"split"`: Render each annotation on its own line
**Example usage**: **Example usage**:
@ -1372,15 +1396,13 @@ limit resolutions to those present on that first index (`first-match`). This pre
"dependency confusion" attacks, whereby an attack can upload a malicious package under the "dependency confusion" attacks, whereby an attack can upload a malicious package under the
same name to a secondary. same name to a secondary.
Possible values:
- `"first-index"`: Only use results from the first index that returns a match for a given package name.
- `"unsafe-first-match"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next.
- `"unsafe-best-match"`: Search for every package name across all indexes, preferring the "best" version found. If a package version is in multiple indexes, only look at the entry for the first index.
**Default value**: `"first-index"` **Default value**: `"first-index"`
**Type**: `str` **Possible values**:
- `"first-index"`: Only use results from the first index that returns a match for a given package name
- `"unsafe-first-match"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next
- `"unsafe-best-match"`: Search for every package name across all indexes, preferring the "best" version found. If a package version is in multiple indexes, only look at the entry for the first index
**Example usage**: **Example usage**:
@ -1497,7 +1519,11 @@ Windows.
**Default value**: `"clone" (macOS) or "hardlink" (Linux, Windows)` **Default value**: `"clone" (macOS) or "hardlink" (Linux, Windows)`
**Type**: `str` **Possible values**:
- `"clone"`: Clone (i.e., copy-on-write) packages from the wheel into the site packages
- `"copy"`: Copy packages from the wheel into the site packages
- `"hardlink"`: Hard link packages from the wheel into the site packages
**Example usage**: **Example usage**:
@ -1906,7 +1932,13 @@ declared specifiers (`if-necessary-or-explicit`).
**Default value**: `"if-necessary-or-explicit"` **Default value**: `"if-necessary-or-explicit"`
**Type**: `str` **Possible values**:
- `"disallow"`: Disallow all pre-release versions
- `"allow"`: Allow all pre-release versions
- `"if-necessary"`: Allow pre-release versions if all versions of a package are pre-release
- `"explicit"`: Allow pre-release versions for first-party packages with explicit pre-release markers in their version requirements
- `"if-necessary-or-explicit"`: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
**Example usage**: **Example usage**:
@ -2121,7 +2153,11 @@ By default, uv will use the latest compatible version of each package (`highest`
**Default value**: `"highest"` **Default value**: `"highest"`
**Type**: `str` **Possible values**:
- `"highest"`: Resolve the highest compatible version of each package
- `"lowest"`: Resolve the lowest compatible version of each package
- `"lowest-direct"`: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies
**Example usage**: **Example usage**:

4
uv.schema.json generated
View file

@ -71,7 +71,7 @@
} }
}, },
"index-strategy": { "index-strategy": {
"description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attack can upload a malicious package under the same name to a secondary.\n\nPossible values:\n\n- `\"first-index\"`: Only use results from the first index that returns a match for a given package name. - `\"unsafe-first-match\"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next. - `\"unsafe-best-match\"`: Search for every package name across all indexes, preferring the \"best\" version found. If a package version is in multiple indexes, only look at the entry for the first index.", "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attack can upload a malicious package under the same name to a secondary.",
"anyOf": [ "anyOf": [
{ {
"$ref": "#/definitions/IndexStrategy" "$ref": "#/definitions/IndexStrategy"
@ -638,7 +638,7 @@
] ]
}, },
"index-strategy": { "index-strategy": {
"description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attack can upload a malicious package under the same name to a secondary.\n\nPossible values:\n\n- `\"first-index\"`: Only use results from the first index that returns a match for a given package name. - `\"unsafe-first-match\"`: Search for every package name across all indexes, exhausting the versions from the first index before moving on to the next. - `\"unsafe-best-match\"`: Search for every package name across all indexes, preferring the \"best\" version found. If a package version is in multiple indexes, only look at the entry for the first index.", "description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-match`). This prevents \"dependency confusion\" attacks, whereby an attack can upload a malicious package under the same name to a secondary.",
"anyOf": [ "anyOf": [
{ {
"$ref": "#/definitions/IndexStrategy" "$ref": "#/definitions/IndexStrategy"