-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Support http/https URLs in uv python --python-downloads-json-url
#14687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
05f956e to
af86992
Compare
|
I'll fix up docs and I can add some test coverage. This lets you do things like The high-level goal here is making it a little bit easier for users to roll back if we unexpectedly break something in python-build-standalone. I also intend to add, likely in separate PRs,
|
crates/uv-python/src/downloads.rs
Outdated
| &self, | ||
| downloader: &'a ManagedPythonDownloader, | ||
| ) -> impl Iterator<Item = &'a ManagedPythonDownload> { | ||
| let s = self.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to clone here? That seems weird?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, I needed this to get past some sort of lifetiime issue because the iterator captures self, but it seems fine now. I must have fixed something else better. I'll get rid of it, thanks!
|
I might need a couple sentence summary in the PR description about the high-level approach here. |
In terms of implementation, or something more like design/intended semantics? In terms of implementation, this is broken down into two commits which you might prefer for reviewing, but the change is basically:
Oh, one note which I forgot to mention is that per In terms of semantics, this breaks the exciting corner case of parsing |
Reviewing #14687, I noticed that we had implemented a `Url::from_url_or_path`-like function, but it wasn't reusable. This change `Verbatim::from_url_or_path` so we can use it in other places too. The PEP 508 parser is an odd place for this, but that's where `VerbatimUrl` and `Scheme` are already living.
Reviewing #14687, I noticed that we had implemented a `Url::from_url_or_path`-like function, but it wasn't reusable. This change `Verbatim::from_url_or_path` so we can use it in other places too. The PEP 508 parser is an odd place for this, but that's where `VerbatimUrl` and `Scheme` are already living.
konstin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a test as mentioned, the code is looking good already.
| }) | ||
| .into_iter() | ||
| .map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref())) | ||
| .map(|a| InstallRequest::new(a, &downloader)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not your fault but I'm only now seeing it.
| .map(|a| InstallRequest::new(a, &downloader)) | |
| .map(|request| InstallRequest::new(request, &downloader)) |
| .iter() | ||
| .map(|target| PythonRequest::parse(target.as_str())) | ||
| .map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref())) | ||
| .map(|a| InstallRequest::new(a, &downloader)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| .map(|a| InstallRequest::new(a, &downloader)) | |
| .map(|request| InstallRequest::new(request, &downloader)) |
crates/uv-python/src/downloads.rs
Outdated
| let s = self.clone(); | ||
| downloader | ||
| .iter_all() | ||
| .filter(move |download| s.satisfied_by_download(download)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| let s = self.clone(); | |
| downloader | |
| .iter_all() | |
| .filter(move |download| s.satisfied_by_download(download)) | |
| downloader | |
| .iter_all() | |
| .filter(move |download| self.satisfied_by_download(download)) |
| .as_ref() | ||
| .map(|a| PythonDownloadRequest::iter_downloads(a, python_downloads_json_url.as_deref())) | ||
| .transpose()? | ||
| .map(|a| PythonDownloadRequest::iter_downloads(a, &downloader)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| .map(|a| PythonDownloadRequest::iter_downloads(a, &downloader)) | |
| .map(|request| request.iter_downloads(&downloader)) |
crates/uv-python/src/downloads.rs
Outdated
| ) -> Result<&'static ManagedPythonDownload, Error> { | ||
| if let Some(download) = request.iter_downloads(python_downloads_json_url)?.next() { | ||
| ) -> Result<&ManagedPythonDownload, Error> { | ||
| if let Some(download) = request.iter_downloads(self).next() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call looks backwards, flipping the method so that the call is self.iter_downloads(request) would look more natural
|
|
||
| let file = fs_err::File::open(json_source.as_ref())?; | ||
| ) -> Result<Self, Error> { | ||
| // Although read_url() handles file:// URLs and converts them to local file reads, here we |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried improving the URL parsing and reusability situation in #14712, but I didn't get anything that's noticeable better than the current logic
Reviewing #14687, I noticed that we had implemented a `Url::from_url_or_path`-like function, but it wasn't reusable. This change `Verbatim::from_url_or_path` so we can use it in other places too. The PEP 508 parser is an odd place for this, but that's where `VerbatimUrl` and `Scheme` are already living.
crates/uv-python/src/downloads.rs
Outdated
| /// | ||
| /// If there is no stable version matching the request, a compatible pre-release version will | ||
| /// be searched for — even if a pre-release was not explicitly requested. | ||
| pub fn from_request( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the rename, this is a little weird? Like ManagedPythonDownloader::from_request -> ManagedPythonDownload when I'd expect from_.. to return Self.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for this and Konsti's comment on "this call looks backwards" I was trying to minimize the amount of textual churn, but probably better to not optimize for that (especially now that reviewers have seen the commits so far :) ), I can rework things a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be resolved by #14687 (comment)
Makes sense. In general, don't be afraid to refactor in uv.
crates/uv-python/src/downloads.rs
Outdated
| } | ||
|
|
||
| impl ManagedPythonDownload { | ||
| impl ManagedPythonDownloader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might rename this to ManagedPythonDownloads then have download_for_requestand iter_downloads methods (or just find and iter)? This doesn't actually download the versions, which is a little misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the rename is also a little confusing because the client you pass to this struct won't be used by ManagedPythonDownload for the actual download
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that occurred to me too. Maybe ManagedPythonDownloadList for a little more visual distinction than the plural?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I initially suggested that too and edited it out. I don't mind either way — ManagedPythonDownloads is a little closer to our typical naming in the project.
| /// Note: The list is generated on the first call to this function. | ||
| /// so `python_downloads_json_url` is only used in the first call to this function. | ||
| pub fn iter_all( | ||
| pub async fn new( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have a docstring. What's the tldr on why this can't just take a BaseClientBuilder? I'm not worried about the performance, but it'd be a little clearer that it doesn't necessarily need a materialized client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at the callsites and it seems like you have a BaseClientBuilder you could provide instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could, the only reason is that two of its callers construct a client shortly after calling this, and so if we passed a builder, we'd be calling .build() twice.. If that's not a problem I can definitely change that (and lazy-construct the client if we're in the HTTP(S) case only). But I was worried I'd have to do something like pass a &mut Option<BaseClient> to pass the constructed client back to the caller to reuse it, or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's probably fine if we build twice.
crates/uv-python/src/downloads.rs
Outdated
| if let Ok(path) = url.to_file_path() { | ||
| Source::Path(Cow::Owned(path)) | ||
| } else { | ||
| return Err(Error::InvalidUrlFormat(url)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd probably write this as a let Ok(...) else return instead of if let Ok(...) else return so you can early return
In preparation for supporting download sources over HTTP, change the implicit creation of the download list to an explicit async constructor. Also pass it a BaseClient which will be used in the next commit.
…wnload list If the JSON does not deserialize, check to see if any key is named "version" and print a different error message hinting at upgrading uv. The intention is that we will add this field on backwards-incompatible changes (and set it to an integer or string or something else that does not deserialize into a JsonPythonDownload).
e7ab6bc to
25dd584
Compare
uv python --python-downloads-json-url
konstin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need a (wiremock) test, otherwise r+ me on the code.
| /// URL pointing to JSON of custom Python installations. | ||
| /// | ||
| /// Note that currently, only local paths are supported. | ||
| /// This can be a local path or file://, http://, or https:// URL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reads like there is a particle missing
From reviewing #14687, I was reminded that every base client operation requires manually destructuring the `NetworkSettings`. We can avoid that by passing the global network settings directly.
From reviewing #14687, I was reminded that every base client operation requires manually destructuring the `NetworkSettings`. We can avoid that by passing the global network settings directly.
From reviewing #14687, I was reminded that every base client operation requires manually destructuring the `NetworkSettings`. We can avoid that by passing the global network settings directly.
|
Hi @geofft , this feature is something I'm very interested in as well. I noticed there hasn't been any recent activity - are you still planning to work on it? If not, I'd be happy to help move it forward or open a continuation PR based on your work. |
No description provided.