From 0021e48dd6d12d0172cfeb006b6e44016c8aa923 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 8 May 2024 11:38:40 -0400 Subject: [PATCH] Improve JSON Schema and add export script --- crates/distribution-types/src/index_url.rs | 12 +- .../uv-configuration/src/name_specifiers.rs | 4 + crates/uv-configuration/src/target_triple.rs | 11 ++ crates/uv-interpreter/src/python_version.rs | 16 ++- crates/uv-normalize/src/extra_name.rs | 4 +- crates/uv-normalize/src/package_name.rs | 4 +- crates/uv-requirements/src/pyproject.rs | 14 +-- crates/uv-resolver/src/exclude_newer.rs | 6 +- scripts/update_schemastore.py | 111 ++++++++++++++++++ uv.schema.json | 35 +++--- 10 files changed, 187 insertions(+), 30 deletions(-) create mode 100644 scripts/update_schemastore.py diff --git a/crates/distribution-types/src/index_url.rs b/crates/distribution-types/src/index_url.rs index 1fee4e0bf78a..1e7e94ae0cc1 100644 --- a/crates/distribution-types/src/index_url.rs +++ b/crates/distribution-types/src/index_url.rs @@ -19,7 +19,7 @@ static PYPI_URL: Lazy = Lazy::new(|| Url::parse("https://pypi.org/simple"). static DEFAULT_INDEX_URL: Lazy = Lazy::new(|| IndexUrl::Pypi(VerbatimUrl::from_url(PYPI_URL.clone()))); -/// The url of an index, newtype'd to avoid mixing it with file urls. +/// The URL of an index to use for fetching packages (e.g., PyPI). #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub enum IndexUrl { Pypi(VerbatimUrl), @@ -36,6 +36,11 @@ impl schemars::JsonSchema for IndexUrl { fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { schemars::schema::SchemaObject { instance_type: Some(schemars::schema::InstanceType::String.into()), + format: Some("uri".to_owned()), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`).".to_string()), + ..schemars::schema::Metadata::default() + })), ..schemars::schema::SchemaObject::default() } .into() @@ -169,6 +174,11 @@ impl schemars::JsonSchema for FlatIndexLocation { fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { schemars::schema::SchemaObject { instance_type: Some(schemars::schema::InstanceType::String.into()), + format: Some("uri".to_owned()), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("The path to a directory of distributions, or a URL to an HTML file with a flat listing of distributions.".to_string()), + ..schemars::schema::Metadata::default() + })), ..schemars::schema::SchemaObject::default() } .into() diff --git a/crates/uv-configuration/src/name_specifiers.rs b/crates/uv-configuration/src/name_specifiers.rs index 1903652bee18..a554e2560b10 100644 --- a/crates/uv-configuration/src/name_specifiers.rs +++ b/crates/uv-configuration/src/name_specifiers.rs @@ -75,6 +75,10 @@ impl schemars::JsonSchema for PackageNameSpecifier { ), ..schemars::schema::StringValidation::default() })), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("The name of a package, or `:all:` or `:none:` to select or omit all packages, respectively.".to_string()), + ..schemars::schema::Metadata::default() + })), ..schemars::schema::SchemaObject::default() } .into() diff --git a/crates/uv-configuration/src/target_triple.rs b/crates/uv-configuration/src/target_triple.rs index 7607dbcd93e1..880b77791981 100644 --- a/crates/uv-configuration/src/target_triple.rs +++ b/crates/uv-configuration/src/target_triple.rs @@ -21,46 +21,57 @@ pub enum TargetTriple { /// An x86 Windows target. #[cfg_attr(feature = "clap", value(name = "x86_64-pc-windows-msvc"))] + #[cfg_attr(feature = "schemars", schemars(rename = "x86_64-pc-windows-msvc"))] X8664PcWindowsMsvc, /// An x86 Linux target. Equivalent to `x86_64-manylinux_2_17`. #[cfg_attr(feature = "clap", value(name = "x86_64-unknown-linux-gnu"))] + #[cfg_attr(feature = "schemars", schemars(rename = "x86_64-unknown-linux-gnu"))] X8664UnknownLinuxGnu, /// An ARM-based macOS target, as seen on Apple Silicon devices. #[cfg_attr(feature = "clap", value(name = "aarch64-apple-darwin"))] + #[cfg_attr(feature = "schemars", schemars(rename = "aarch64-apple-darwin"))] Aarch64AppleDarwin, /// An x86 macOS target. #[cfg_attr(feature = "clap", value(name = "x86_64-apple-darwin"))] + #[cfg_attr(feature = "schemars", schemars(rename = "x86_64-apple-darwin"))] X8664AppleDarwin, /// An ARM64 Linux target. Equivalent to `aarch64-manylinux_2_17`. #[cfg_attr(feature = "clap", value(name = "aarch64-unknown-linux-gnu"))] + #[cfg_attr(feature = "schemars", schemars(rename = "aarch64-unknown-linux-gnu"))] Aarch64UnknownLinuxGnu, /// An ARM64 Linux target. #[cfg_attr(feature = "clap", value(name = "aarch64-unknown-linux-musl"))] + #[cfg_attr(feature = "schemars", schemars(rename = "aarch64-unknown-linux-musl"))] Aarch64UnknownLinuxMusl, /// An `x86_64` Linux target. #[cfg_attr(feature = "clap", value(name = "x86_64-unknown-linux-musl"))] + #[cfg_attr(feature = "schemars", schemars(rename = "x86_64-unknown-linux-musl"))] X8664UnknownLinuxMusl, /// An `x86_64` target for the `manylinux_2_17` platform. #[cfg_attr(feature = "clap", value(name = "x86_64-manylinux_2_17"))] + #[cfg_attr(feature = "schemars", schemars(rename = "x86_64-manylinux_2_17"))] X8664Manylinux217, /// An `x86_64` target for the `manylinux_2_28` platform. #[cfg_attr(feature = "clap", value(name = "x86_64-manylinux_2_28"))] + #[cfg_attr(feature = "schemars", schemars(rename = "x86_64-manylinux_2_28"))] X8664Manylinux228, /// An ARM64 target for the `manylinux_2_17` platform. #[cfg_attr(feature = "clap", value(name = "aarch64-manylinux_2_17"))] + #[cfg_attr(feature = "schemars", schemars(rename = "aarch64-manylinux_2_17"))] Aarch64Manylinux217, /// An ARM64 target for the `manylinux_2_28` platform. #[cfg_attr(feature = "clap", value(name = "aarch64-manylinux_2_28"))] + #[cfg_attr(feature = "schemars", schemars(rename = "aarch64-manylinux_2_28"))] Aarch64Manylinux228, } diff --git a/crates/uv-interpreter/src/python_version.rs b/crates/uv-interpreter/src/python_version.rs index 76a016696aad..dc8ab7373b07 100644 --- a/crates/uv-interpreter/src/python_version.rs +++ b/crates/uv-interpreter/src/python_version.rs @@ -48,8 +48,20 @@ impl schemars::JsonSchema for PythonVersion { String::from("PythonVersion") } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - ::json_schema(gen) + fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + string: Some(Box::new(schemars::schema::StringValidation { + pattern: Some(r"^3\.\d+(\.\d+)?$".to_string()), + ..schemars::schema::StringValidation::default() + })), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("A Python version specifier, e.g. `3.7` or `3.8.0`.".to_string()), + ..schemars::schema::Metadata::default() + })), + ..schemars::schema::SchemaObject::default() + } + .into() } } diff --git a/crates/uv-normalize/src/extra_name.rs b/crates/uv-normalize/src/extra_name.rs index 0d8660f7a877..ecc4a9cc4d51 100644 --- a/crates/uv-normalize/src/extra_name.rs +++ b/crates/uv-normalize/src/extra_name.rs @@ -8,8 +8,8 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam /// The normalized name of an extra dependency group. /// -/// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.` -/// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`. +/// Converts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. +/// For example, `---`, `.`, and `__` are all converted to a single `-`. /// /// See: /// - diff --git a/crates/uv-normalize/src/package_name.rs b/crates/uv-normalize/src/package_name.rs index 6ebd41701052..9fcc50152c2a 100644 --- a/crates/uv-normalize/src/package_name.rs +++ b/crates/uv-normalize/src/package_name.rs @@ -7,8 +7,8 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam /// The normalized name of a package. /// -/// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.` -/// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`. +/// Converts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. +/// For example, `---`, `.`, and `__` are all converted to a single `-`. /// /// See: #[derive( diff --git a/crates/uv-requirements/src/pyproject.rs b/crates/uv-requirements/src/pyproject.rs index 0c32ed606acf..ca92cc5d579d 100644 --- a/crates/uv-requirements/src/pyproject.rs +++ b/crates/uv-requirements/src/pyproject.rs @@ -1,9 +1,10 @@ -//! Reading from `pyproject.toml` -//! * `project.{dependencies,optional-dependencies}`, -//! * `tool.uv.sources` and +//! Reads the following fields from from `pyproject.toml`: +//! +//! * `project.{dependencies,optional-dependencies}` +//! * `tool.uv.sources` //! * `tool.uv.workspace` //! -//! and lowering them into a dependency specification. +//! Then lowers them into a dependency specification. use std::collections::HashMap; use std::io; @@ -75,7 +76,7 @@ pub enum LoweringError { pub struct PyProjectToml { /// PEP 621-compliant project metadata. pub project: Option, - /// Proprietary additions. + /// Tool-specific metadata. pub tool: Option, } @@ -99,14 +100,12 @@ pub struct Project { pub dynamic: Option>, } -/// `tool`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Tool { pub uv: Option, } -/// `tool.uv`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(deny_unknown_fields)] @@ -115,7 +114,6 @@ pub struct ToolUv { pub workspace: Option, } -/// `tool.uv.workspace`. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(deny_unknown_fields)] diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs index 50f99893c405..d2fd6b3582b3 100644 --- a/crates/uv-resolver/src/exclude_newer.rs +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -67,7 +67,11 @@ impl schemars::JsonSchema for ExcludeNewer { ), ..schemars::schema::StringValidation::default() })), - ..Default::default() + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same format (e.g., `2006-12-02`).".to_string()), + ..schemars::schema::Metadata::default() + })), + ..schemars::schema::SchemaObject::default() } .into() } diff --git a/scripts/update_schemastore.py b/scripts/update_schemastore.py new file mode 100644 index 000000000000..b357d5dafe04 --- /dev/null +++ b/scripts/update_schemastore.py @@ -0,0 +1,111 @@ +"""Update uv.json in schemastore. + +This script will clone astral-sh/schemastore, update the schema and push the changes +to a new branch tagged with the uv git hash. You should see a URL to create the PR +to schemastore in the CLI. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from subprocess import check_call, check_output +from tempfile import TemporaryDirectory + +SCHEMASTORE_FORK = "git@github.com:astral-sh/schemastore.git" +SCHEMASTORE_UPSTREAM = "git@github.com:SchemaStore/schemastore.git" +UV_REPOSITORY = "https://github.com/astral-sh/uv" +UV_JSON_PATH = Path("schemas/json/uv.json") + + +def update_schemastore(schemastore: Path, *, root: Path) -> None: + if not schemastore.is_dir(): + check_call(["git", "clone", SCHEMASTORE_FORK, schemastore]) + check_call( + [ + "git", + "remote", + "add", + "upstream", + SCHEMASTORE_UPSTREAM, + ], + cwd=schemastore, + ) + # Create a new branch tagged with the current uv commit up to date with the latest + # upstream schemastore + check_call(["git", "fetch", "upstream"], cwd=schemastore) + current_sha = check_output(["git", "rev-parse", "HEAD"], text=True).strip() + branch = f"update-uv-{current_sha}" + check_call( + ["git", "switch", "-c", branch], + cwd=schemastore, + ) + check_call( + ["git", "reset", "--hard", "upstream/master"], + cwd=schemastore, + ) + + # Run npm install + src = schemastore.joinpath("src") + check_call(["npm", "install"], cwd=src) + + # Update the schema and format appropriately + schema = json.loads(root.joinpath("uv.schema.json").read_text()) + schema["$id"] = "https://json.schemastore.org/uv.json" + src.joinpath(UV_JSON_PATH).write_text( + json.dumps(dict(schema.items()), indent=2, ensure_ascii=False), + ) + check_call( + [ + "node_modules/.bin/prettier", + "--plugin", + "prettier-plugin-sort-json", + "--write", + UV_JSON_PATH, + ], + cwd=src, + ) + + # Check if the schema has changed + # https://stackoverflow.com/a/9393642/3549270 + if check_output(["git", "status", "-s"], cwd=schemastore).strip(): + # Schema has changed, commit and push + commit_url = f"{UV_REPOSITORY}/commit/{current_sha}" + commit_body = f"This updates uv's JSON schema to [{current_sha}]({commit_url})" + # https://stackoverflow.com/a/22909204/3549270 + check_call( + [ + "git", + "commit", + "-a", + "-m", + "Update uv's JSON schema", + "-m", + commit_body, + ], + cwd=schemastore, + ) + # This should show the link to create a PR + check_call( + ["git", "push", "--set-upstream", "origin", branch], + cwd=schemastore, + ) + else: + print("No changes") + + +def main() -> None: + root = Path( + check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip(), + ) + + schemastore = root.joinpath("schemastore") + if schemastore.is_dir(): + update_schemastore(schemastore, root=root) + else: + with TemporaryDirectory() as temp_dir: + update_schemastore(Path(temp_dir).joinpath("schemastore")) + + +if __name__ == "__main__": + main() diff --git a/uv.schema.json b/uv.schema.json index 65c769142e17..c4da81be002d 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -120,15 +120,18 @@ } }, "ExcludeNewer": { + "description": "Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same format (e.g., `2006-12-02`).", "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))?$" }, "ExtraName": { - "description": "The normalized name of an extra dependency group.\n\nConverts the name to lowercase and collapses any run of the characters `-`, `_` and `.` down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`.\n\nSee: - - ", + "description": "The normalized name of an extra dependency group.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. For example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: - - ", "type": "string" }, "FlatIndexLocation": { - "type": "string" + "description": "The path to a directory of distributions, or a URL to an HTML file with a flat listing of distributions.", + "type": "string", + "format": "uri" }, "IndexStrategy": { "oneOf": [ @@ -156,7 +159,9 @@ ] }, "IndexUrl": { - "type": "string" + "description": "The URL of an index to use for fetching packages (e.g., `https://pypi.org/simple`).", + "type": "string", + "format": "uri" }, "KeyringProviderType": { "description": "Keyring provider type to use for credential lookup.", @@ -203,10 +208,11 @@ ] }, "PackageName": { - "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses any run of the characters `-`, `_` and `.` down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`.\n\nSee: ", + "description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`. For example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: ", "type": "string" }, "PackageNameSpecifier": { + "description": "The name of a package, or `:all:` or `:none:` to select or omit all packages, respectively.", "type": "string", "pattern": "^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$" }, @@ -565,7 +571,9 @@ ] }, "PythonVersion": { - "type": "string" + "description": "A Python version specifier, e.g. `3.7` or `3.8.0`.", + "type": "string", + "pattern": "^3\\.\\d+(\\.\\d+)?$" }, "ResolutionMode": { "oneOf": [ @@ -796,14 +804,14 @@ "description": "An x86 Windows target.", "type": "string", "enum": [ - "x8664-pc-windows-msvc" + "x86_64-pc-windows-msvc" ] }, { "description": "An x86 Linux target. Equivalent to `x86_64-manylinux_2_17`.", "type": "string", "enum": [ - "x8664-unknown-linux-gnu" + "x86_64-unknown-linux-gnu" ] }, { @@ -817,7 +825,7 @@ "description": "An x86 macOS target.", "type": "string", "enum": [ - "x8664-apple-darwin" + "x86_64-apple-darwin" ] }, { @@ -838,41 +846,40 @@ "description": "An `x86_64` Linux target.", "type": "string", "enum": [ - "x8664-unknown-linux-musl" + "x86_64-unknown-linux-musl" ] }, { "description": "An `x86_64` target for the `manylinux_2_17` platform.", "type": "string", "enum": [ - "x8664-manylinux217" + "x86_64-manylinux_2_17" ] }, { "description": "An `x86_64` target for the `manylinux_2_28` platform.", "type": "string", "enum": [ - "x8664-manylinux228" + "x86_64-manylinux_2_28" ] }, { "description": "An ARM64 target for the `manylinux_2_17` platform.", "type": "string", "enum": [ - "aarch64-manylinux217" + "aarch64-manylinux_2_17" ] }, { "description": "An ARM64 target for the `manylinux_2_28` platform.", "type": "string", "enum": [ - "aarch64-manylinux228" + "aarch64-manylinux_2_28" ] } ] }, "ToolUvWorkspace": { - "description": "`tool.uv.workspace`.", "type": "object", "properties": { "exclude": {