mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-24 05:17:05 +00:00
Hint at wrong endpoint in publish (#7872)
Improve hints when using the simple index URL instead of the upload URL in `uv publish`. This is the most common confusion when publishing, so we give it some extra care and put it more centrally in the CLI help. Fixes #7860 --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
9e98055a9e
commit
282fab5f70
3 changed files with 34 additions and 14 deletions
|
|
@ -4449,7 +4449,8 @@ pub struct DisplayTreeArgs {
|
|||
#[arg(long)]
|
||||
pub no_dedupe: bool,
|
||||
|
||||
/// Show the reverse dependencies for the given package. This flag will invert the tree and display the packages that depend on the given package.
|
||||
/// Show the reverse dependencies for the given package. This flag will invert the tree and
|
||||
/// display the packages that depend on the given package.
|
||||
#[arg(long, alias = "reverse")]
|
||||
pub invert: bool,
|
||||
}
|
||||
|
|
@ -4463,9 +4464,10 @@ pub struct PublishArgs {
|
|||
#[arg(default_value = "dist/*")]
|
||||
pub files: Vec<String>,
|
||||
|
||||
/// The URL of the upload endpoint.
|
||||
/// The URL of the upload endpoint (not the index URL).
|
||||
///
|
||||
/// Note that this typically differs from the index URL.
|
||||
/// Note that there are typically different URLs for index access (e.g., `https:://.../simple`)
|
||||
/// and index upload.
|
||||
///
|
||||
/// Defaults to PyPI's publish URL (<https://upload.pypi.org/legacy/>).
|
||||
///
|
||||
|
|
|
|||
|
|
@ -81,8 +81,12 @@ pub enum PublishSendError {
|
|||
ReqwestMiddleware(#[from] reqwest_middleware::Error),
|
||||
#[error("Upload failed with status {0}")]
|
||||
StatusNoBody(StatusCode, #[source] reqwest::Error),
|
||||
#[error("Upload failed with status code {0}: {1}")]
|
||||
#[error("Upload failed with status code {0}. Server says: {1}")]
|
||||
Status(StatusCode, String),
|
||||
#[error("POST requests are not supported by the endpoint, are you using the simple index URL instead of the upload URL?")]
|
||||
MethodNotAllowedNoBody,
|
||||
#[error("POST requests are not supported by the endpoint, are you using the simple index URL instead of the upload URL? Server says: {0}")]
|
||||
MethodNotAllowed(String),
|
||||
/// The registry returned a "403 Forbidden".
|
||||
#[error("Permission denied (status code {0}): {1}")]
|
||||
PermissionDenied(StatusCode, String),
|
||||
|
|
@ -577,18 +581,32 @@ async fn handle_response(registry: &Url, response: Response) -> Result<bool, Pub
|
|||
.get(reqwest::header::CONTENT_TYPE)
|
||||
.and_then(|content_type| content_type.to_str().ok())
|
||||
.map(ToString::to_string);
|
||||
let upload_error = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|err| PublishSendError::StatusNoBody(status_code, err))?;
|
||||
let upload_error = response.bytes().await.map_err(|err| {
|
||||
if status_code == StatusCode::METHOD_NOT_ALLOWED {
|
||||
PublishSendError::MethodNotAllowedNoBody
|
||||
} else {
|
||||
PublishSendError::StatusNoBody(status_code, err)
|
||||
}
|
||||
})?;
|
||||
let upload_error = String::from_utf8_lossy(&upload_error);
|
||||
|
||||
trace!("Response content for non-200 for {registry}: {upload_error}");
|
||||
trace!("Response content for non-200 response for {registry}: {upload_error}");
|
||||
|
||||
debug!("Upload error response: {upload_error}");
|
||||
|
||||
// That's most likely the simple index URL, not the upload URL.
|
||||
if status_code == StatusCode::METHOD_NOT_ALLOWED {
|
||||
return Err(PublishSendError::MethodNotAllowed(
|
||||
PublishSendError::extract_error_message(
|
||||
upload_error.to_string(),
|
||||
content_type.as_deref(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Detect existing file errors the way twine does.
|
||||
// https://github.com/pypa/twine/blob/c512bbf166ac38239e58545a39155285f8747a7b/twine/commands/upload.py#L34-L72
|
||||
if status_code == 403 {
|
||||
if status_code == StatusCode::FORBIDDEN {
|
||||
if upload_error.contains("overwrite artifact") {
|
||||
// Artifactory (https://jfrog.com/artifactory/)
|
||||
Ok(false)
|
||||
|
|
@ -601,10 +619,10 @@ async fn handle_response(registry: &Url, response: Response) -> Result<bool, Pub
|
|||
),
|
||||
))
|
||||
}
|
||||
} else if status_code == 409 {
|
||||
} else if status_code == StatusCode::CONFLICT {
|
||||
// conflict, pypiserver (https://pypi.org/project/pypiserver)
|
||||
Ok(false)
|
||||
} else if status_code == 400
|
||||
} else if status_code == StatusCode::BAD_REQUEST
|
||||
&& (upload_error.contains("updating asset") || upload_error.contains("already been taken"))
|
||||
{
|
||||
// Nexus Repository OSS (https://www.sonatype.com/nexus-repository-oss)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue