diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index e7e1e2090edd..e9b9b5593a94 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -6,7 +6,7 @@ use uv_configuration::{ ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple, TrustedHost, TrustedPublishing, }; -use uv_distribution_types::{Index, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata}; +use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata}; use uv_install_wheel::linker::LinkMode; use uv_macros::{CombineOptions, OptionsMetadata}; use uv_normalize::{ExtraName, PackageName}; @@ -1545,6 +1545,7 @@ pub struct OptionsWire { // publish: PublishOptions publish_url: Option, trusted_publishing: Option, + check_url: Option, pip: Option, cache_keys: Option>, @@ -1613,6 +1614,7 @@ impl From for Options { environments, publish_url, trusted_publishing, + check_url, workspace, sources, default_groups, @@ -1671,6 +1673,7 @@ impl From for Options { publish: PublishOptions { publish_url, trusted_publishing, + check_url, }, workspace, sources, @@ -1712,4 +1715,26 @@ pub struct PublishOptions { "# )] pub trusted_publishing: Option, + + /// Check an index URL for existing files to skip duplicate uploads. + /// + /// This option allows retrying publishing that failed after only some, but not all files have + /// been uploaded, and handles error due to parallel uploads of the same file. + /// + /// Before uploading, the index is checked. If the exact same file already exists in the index, + /// the file will not be uploaded. If an error occurred during the upload, the index is checked + /// again, to handle cases where the identical file was uploaded twice in parallel. + /// + /// The exact behavior will vary based on the index. When uploading to PyPI, uploading the same + /// file succeeds even without `--check-url`, while most other indexes error. + /// + /// The index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512). + #[option( + default = "None", + value_type = "str", + example = r#" + check-url = "https://test.pypi.org/simple" + "# + )] + pub check_url: Option, } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 1ea29d8172fd..70be218dc698 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2665,6 +2665,7 @@ impl PublishSettings { let PublishOptions { publish_url, trusted_publishing, + check_url, } = publish; let ResolverInstallerOptions { keyring_provider, .. @@ -2692,7 +2693,9 @@ impl PublishSettings { .keyring_provider .combine(keyring_provider) .unwrap_or_default(), - check_url: args.check_url, + check_url: args + .check_url + .combine(check_url), } } } diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 47e2f8d0b8d1..4d5670ac56da 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -191,7 +191,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> { | 2 | unknown = "field" | ^^^^^^^ - unknown field `unknown`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` + unknown field `unknown`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` Resolved in [TIME] Audited in [TIME] diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index a435be824d4e..e67313aacb53 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -3229,7 +3229,7 @@ fn resolve_config_file() -> anyhow::Result<()> { | 1 | [project] | ^^^^^^^ - unknown field `project`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` + unknown field `project`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies` "### ); diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 7e07811452fc..1784daaba0fd 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -406,6 +406,42 @@ globs are interpreted as relative to the project directory. --- +### [`check-url`](#check-url) {: #check-url } + +Check an index URL for existing files to skip duplicate uploads. + +This option allows retrying publishing that failed after only some, but not all files have +been uploaded, and handles error due to parallel uploads of the same file. + +Before uploading, the index is checked. If the exact same file already exists in the index, +the file will not be uploaded. If an error occurred during the upload, the index is checked +again, to handle cases where the identical file was uploaded twice in parallel. + +The exact behavior will vary based on the index. When uploading to PyPI, uploading the same +file succeeds even without `--check-url`, while most other indexes error. + +The index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512). + +**Default value**: `None` + +**Type**: `str` + +**Example usage**: + +=== "pyproject.toml" + + ```toml + [tool.uv] + check-url = "https://test.pypi.org/simple" + ``` +=== "uv.toml" + + ```toml + check-url = "https://test.pypi.org/simple" + ``` + +--- + ### [`compile-bytecode`](#compile-bytecode) {: #compile-bytecode } Compile Python files to bytecode after installation. diff --git a/uv.schema.json b/uv.schema.json index ecf4e8b620ce..7aee5f7cbbec 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -31,6 +31,17 @@ "$ref": "#/definitions/CacheKey" } }, + "check-url": { + "description": "Check an index URL for existing files to skip duplicate uploads.\n\nThis option allows retrying publishing that failed after only some, but not all files have been uploaded, and handles error due to parallel uploads of the same file.\n\nBefore uploading, the index is checked. If the exact same file already exists in the index, the file will not be uploaded. If an error occurred during the upload, the index is checked again, to handle cases where the identical file was uploaded twice in parallel.\n\nThe exact behavior will vary based on the index. When uploading to PyPI, uploading the same file succeeds even without `--check-url`, while most other indexes error.\n\nThe index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).", + "anyOf": [ + { + "$ref": "#/definitions/IndexUrl" + }, + { + "type": "null" + } + ] + }, "compile-bytecode": { "description": "Compile Python files to bytecode after installation.\n\nBy default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`); instead, compilation is performed lazily the first time a module is imported. For use-cases in which start time is critical, such as CLI applications and Docker containers, this option can be enabled to trade longer installation times for faster start times.\n\nWhen enabled, uv will process the entire site-packages directory (including packages that are not being modified by the current operation) for consistency. Like pip, it will also ignore errors.", "type": [