Add a PyPiFetchStrategy to properly download Python packages#2718
Add a PyPiFetchStrategy to properly download Python packages#2718adamjstewart wants to merge 1 commit intospack:developfrom
Conversation
| if checksum: | ||
| kwargs['md5'] = checksum | ||
|
|
||
| kwargs['version'] = ver |
There was a problem hiding this comment.
Wasn't sure how to access version in a FetchStrategy.
There was a problem hiding this comment.
Hmm... after 20 minutes of poking around the code, FetchStrategy is mysterious to me. I haven't been able to answer basic questions like:
-
Where are different
FetchStrategys instantiated? I can seeURLFetchStrategyinfrom_url(). But a grep forGitFetchStrategyyields nothing obvious. Is the code being super-clever somewhere and turning the string"git"into the classGitFetchStrategy? -
What is the API for
FetchStrategy? Clearly, the package name and version should be available to fetch strategies in a clean way. (As for package name... both the Spack package name and the download package name should be available. They are not always the same.)
There was a problem hiding this comment.
It's still mysterious to me as well. I believe this function determines which FetchStrategy is used, although I'm not sure where it is instantiated.
There was a problem hiding this comment.
Good detective work! Can you please add the word FetchStrategy to the comments on that function, explaining this? That way when someone greps for FetchStrategy they will find it.
| for release in releases: | ||
| if release['packagetype'] == 'sdist' or \ | ||
| release['python_version'] == 'source': | ||
| return release['url'] |
There was a problem hiding this comment.
This shouldn't need to be duplicated. I'll have to see why it is still calling url_for_version.
There was a problem hiding this comment.
The FetchStrategy stuff is very opaque to me. I don't know when or how Spack decides which FetchStrategy ot instantiate. Maybe that's why.
|
|
||
| # The xmlrpclib module has been renamed to xmlrpc.client in Python 3 | ||
| # Spack does not yet support Python 3, but there's no reason not to be | ||
| # future-proof |
There was a problem hiding this comment.
Would it be better to use six here, rather than rolling our own Python2-vs-Python3 compatibility? six is all in one file and MIT licensed, really easy (and legal) to just include with Spack. With six, we just need:
import six.moves.xmlrpc_client as xmlrpclib
But... where I work, we are not allowed to rename packages on import (for good reason). So I'd prefer:
from six.moves import xmlrpc_client
There was a problem hiding this comment.
Adding another external package seems like overkill for this. I'd rather just drop the Python 3 support.
| if checksum: | ||
| kwargs['md5'] = checksum | ||
|
|
||
| kwargs['version'] = ver |
There was a problem hiding this comment.
Hmm... after 20 minutes of poking around the code, FetchStrategy is mysterious to me. I haven't been able to answer basic questions like:
-
Where are different
FetchStrategys instantiated? I can seeURLFetchStrategyinfrom_url(). But a grep forGitFetchStrategyyields nothing obvious. Is the code being super-clever somewhere and turning the string"git"into the classGitFetchStrategy? -
What is the API for
FetchStrategy? Clearly, the package name and version should be available to fetch strategies in a clean way. (As for package name... both the Spack package name and the download package name should be available. They are not always the same.)
|
|
||
| def __init__(self, pypi=None, digest=None, **kwargs): | ||
| # If pypi or digest are provided in the kwargs, then prefer | ||
| # those values. |
There was a problem hiding this comment.
If pypi is provided in **kwargs, then Python would raise an exception and __init__() would never get called. So no need for the checking code...
| # those values. | ||
| self.pypi = kwargs.get('pypi', None) | ||
| if not self.pypi: | ||
| self.pypi = pypi |
There was a problem hiding this comment.
These three lines should just be: self.pypi = pypi.
There was a problem hiding this comment.
I know something is fishy here, but that's how URLFetchStrategy did things, so I copied it just to be safe. Honestly, all of the FetchStrategy logic needs to be reworked. As you've noticed, it's very opaque and not well documented.
There was a problem hiding this comment.
The first thing I noticed when I looked at Spack code was that it did not use normal Python kwarg declarations. I asked @tgamblin; he told me there was no good reason for this, and that we can start using normal Python. I recommend we do that; when you read a program and you come across something unusual, you ask "why". If there's no good reason for it, we're all better off without that unusual thing in place.
| if not self.digest: | ||
| self.digest = digest | ||
|
|
||
| self.expand_archive = kwargs.get('expand', True) |
There was a problem hiding this comment.
Please use standard Python for this. You should just add expand=True to the arg list. Then you just need self.expand_archive = expand here. Also.. please consider calling the variable self.expand, so you don't have spurious changes of variable name.
There was a problem hiding this comment.
I just copied this line over from URLFetchStrategy.
There was a problem hiding this comment.
Maybe we can fix URLFetchStrategy too. But even if we don't, no need to propagate this stuff.
|
|
||
| self.digest = kwargs.get('md5', None) | ||
| if not self.digest: | ||
| self.digest = digest |
| super(PyPiFetchStrategy, self).__init__( | ||
| url=self.url, digest=self.digest, **kwargs) | ||
|
|
||
| def query_for_url(self): |
There was a problem hiding this comment.
Please see main comment section on this function...
| method.__name__) | ||
|
|
||
|
|
||
| class PyPiFetchError(FetchError): |
There was a problem hiding this comment.
This class is only instantiated once. Unless we explicitly check for it elsewhere, we don't really need this. Just put raise FetchError(...) where you have raise PyPiFetchError(...) above.
| # If we have no idea, try to substitute the version. | ||
| return spack.url.substitute_version( | ||
| self.nearest_url(version), self.url_version(version)) | ||
| if hasattr(cls, 'url') or self.version_urls(): |
There was a problem hiding this comment.
Please don't repeat the PyPI database code here. Must be factored...
| return spack.url.substitute_version( | ||
| self.nearest_url(version), self.url_version(version)) | ||
| elif hasattr(cls, 'pypi') or hasattr(self, 'pypi'): | ||
| client = xmlrpclib.ServerProxy('https://pypi.python.org/pypi') |
There was a problem hiding this comment.
So the UI is to do:
class MyPackage(Package):
url='http://...'
class MyPyPIPackage(Package):
pypi='packagename'
Why not consider this alternative:
class MyPackage(Package):
url='pypi://packagename'
I think we should at least consider the pros and cons of these two different UIs.
| for release in releases: | ||
| if release['packagetype'] == 'sdist' or \ | ||
| release['python_version'] == 'source': | ||
| return release['url'] |
There was a problem hiding this comment.
The FetchStrategy stuff is very opaque to me. I don't know when or how Spack decides which FetchStrategy ot instantiate. Maybe that's why.
|
My big comment is... please create a new file called
So... I'm wondering what messing with
Here is the Spack source file |
|
On Mon, Jan 2, 2017 at 10:24 PM, Adam J. Stewart ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In lib/spack/spack/fetch_strategy.py:
> @@ -57,6 +57,15 @@
from spack.util.compression import decompressor_for, extension
import spack.util.pattern as pattern
+
+# The xmlrpclib module has been renamed to xmlrpc.client in Python 3
+# Spack does not yet support Python 3, but there's no reason not to be
+# future-proof
Adding another external package seems like overkill for this. I'd rather
just drop the Python 3 support.
No, we will need it someday, and then we'll be glad we've already included
the single file `six.py`.
|
|
Honestly, after seeing how bad FetchStrategy is, I'm wondering if we shouldn't just add a |
I'd be happy to throw up a PR with that approach (cutting-n-pasting heavily from this PR), see what we think of it. |
|
How will it handle mirrors properly? Honestly I don't think that is the right approach. If you want some documentation for FetchStrategy, I could help with that. |
|
OK, let's take stock of what's good and bad about this PR so far.
I'm happy with the `pypi.get_url()` function I wrote above (based on
original code from @adamjstewart). I think we should use that function, or
something close to it.
If using this function directly in `url_for_version()` breaks mirrors, then
we should not use it there (something both @adamjstewart and I have
proposed, but maybe that's not what we want). At this point, I don't even
understand why it would break things.
I agree, more docs on `FetchStrategy` --- and how it relates to mirrors ---
would be key to moving this PR forward.
…On Tue, Jan 3, 2017 at 10:42 AM, Todd Gamblin ***@***.***> wrote:
How will it handle mirrors properly? Honestly I don't think that is the
right approach. If you want some documentation for FetchStrategy, I could
help with that.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#2718 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AB1cdyodibGuf_OhfYGz2inzW_5X24_jks5rOmyDgaJpZM4LZPkv>
.
|
|
As for @citibeth's proposed
All I'm doing is looking for a download that matches the package name, version, and checksum that were used when the version was added to the package. I think this is pretty safe and foolproof.
This is in no way the case. There is no logic in Spack that prefers Of course, if PyPi is guaranteed to have a class py-numpy(PythonPackage):
pypi = 'numpy'
version('1.2.3', 'hash')They want to add a new version, so they download Unfortunately, PyPi does not have a |
|
@tgamblin Would you be willing to refactor Another complaint I have is that By the way, on the note of checksumming, why do we trust Git but not PyPi? P.S. Just noticed that |
OK, I was not clear... what I meant is, we've been served just fine by URLs with The function I provided allows for the desired extension to be specified. That provides the direct analog to way we already do things with URLs.
The same problem could happen today: if I download But... I think you are right, users should not be left guessing which extension will be downloaded. Therefore, the
There are many ways this could happen. Using the low-tech approach (which it looks like we probably won't use because it messes up mirrors): Or a more advanced riff, based on the UI you've suggested above: Or even: In this case, Spack would just turn 'datalog.zip' into ('datalog', '.zip') via In this case, Spack would turn
Advantages:
The above discussion is about preference. But there's also a simple bug in the original that should be addressed: if nothing matches the given MD5 digest and there's a |
|
Putting implementation details aside and thinking about UI... I'm coming to believe the best approach is to "create" a we do: or even: I think this will feel simplest and cleanest for users. @davydden do you have an opinion here? Thinking about implementation just a bit... this kind of is still a URL fetch strategy because --- at the end of the day --- a URL is still constructed and fetched. |
|
@tgamblin Can you please expand on the possible issues with mirrors? I'm
incredulous that it would be such a problem. PyPI uses URLs just like
other websites; the only difference is those URLs are hard to guess, just
given the package name, version number and extension. So we go to the PyPI
database to determine the URL. But once it's been determined, it is a
perfectly valid URL that can be fetched with `URLFetchStrategy`.
So once again... I'm convincing myself that we don't need a new fetch
strategy, just a new way to construct URLs for `URLFetchStrategy`. Please
tell me what I'm missing!
|
|
My impression was that url_for_version would make an XML RPC call to the PyPI API. If it doesn't, and if the full URL can be constructed statically then we're mostly good. Spack just needs to be able to construct URLs a priori to function properly with a mirror. One consequence of this that can cause issues is if the remote site uses a forwarding URL that does not include the file extension. The path to an archive in a mirror is If you can meet all those criteria then I think your plan will work. It is some of the oldest spack code but it actually works and even picks up downloads where they left off, so it will get you some useful functionality if you can do it this way. |
|
On Tue, Jan 3, 2017 at 12:14 PM, Todd Gamblin ***@***.***> wrote:
My impression was that url_for_version would make an XML RPC call to the
PyPI API. If it doesn't, and if the full URL can be constructed statically
then we're mostly good. Spack just needs to be able to construct URLs a
priori to function properly with a mirror.
OK, that's the problem. Yes, it needs to go out to the web to find the URL.
One consequence of this that can cause issues is if the remote site uses a
forwarding URL that does not include the file extension. The path to an
archive in a mirror is pkgname-version.extension. The extension must stay
the same so we know (easily) how to expand the file. We could expand and
recompress with a known compression algorithm, but that makes it hard to
know the checksum of what's in the mirror, so we just preserve the archive
as-is. The compromise is that the packager is expected to supply an
extension= arg to version() for projects that use extensionless forwarding
URLs -- look at mfem.
Is this a problem with PyPI? We're dealing with one site here. I'm not
even sure if they use forwarding URLs at all.
If you can meet all those criteria then I think your plan will work.
I'm still left wondering in this case. Trying it out wouldn't be so hard,
I suppose. Do you have suggestions on what test to do?
|
|
We're discussing multiple issues here, let's please not confuse them. How to Query PyPI Databse
That's never been the way we download things before. Up until now, we download things based on the name and extension. If we're going to change that, I believe we should discuss it a bit:
It explicitly fails in that case, by design. I'm hoping there will not be more than one listed file marked
The function I'm advocating for works under-the-hood, and makes no requirements on how packages will look. How Packages Should LookFirst of all, let's get straight that I'm my most preferred way for packages to look is (see above):
I think this is worth a discussion. However, this would be by no means the first system to use not-so-common protocol specifiers in URLs. In the past I've seen If we really don't like custom protocols in URLs, an alternative I suggested would be something like this: Auto-DefaultsA package is fetched based on a package name, version and file extension. Version is non-debatable. So the question is, should there be auto-defaults for the package name and the file extension? And if so, how should Spack determine those defaults? Let's step back for a minute and think of other large repositories we download from --- GNU, Fedora, etc. Except for the hard-to-guess URLs, they share many relevant properties with PyPI:
All of the automagic suggested in this PR could also be implemented for these other repositories. We could automagically guess remote package names (or encourage our package names to be the same as the remote ones). We could automagically determine filename extensions based on checksum. Yes, we could do all that automagic. But we don't. And we shouldn't. Because URLs work just fine, even though you have to manually specify the name of the package in the remote repo you are looking for --- and the extension as well.
Things are simpler and more intuitive when there is less automagic, and when more things are explicit. If every package states what it will download from PyPI, then things are crystal clear to the user. Auto-Defaults for Extension
I am not in favor of this. It's not that hard to type Auto-Defauts for Name
I'm also not in favor of this automagic; although I'm less inclined against it than with extensions. We have quite a number of packages that are named differently in Spack vs. PyPI. Maybe we shouldn't, and maybe we should try to come closer to PyPI. But that's not where we are right now. Again... what's the benefit here? |
Edit: I might be wrong about this. |
|
@tgamblin I'm not sure if I understand the problem with mirrors either. The download tarballs include the extension in their name. Won't |
|
@adamjstewart: yes, but I see a problem here -- you don't know the download extension until you talk to PyPI right? That would prevent us from knowing the file's name in advance. Spack searches several places for your tarball:
Mirrors are specified only by their root, so the first two methods require Spack to construct a path for the archive within the mirror root. Currently, that is
3 and 4 are related -- we preserve the extension because we must preserve the original file intact, or its checksum will change. preserving the extension tells us how it was compressed so we can expand it after downloading. If we can't figure out a way to get PyPI working, we may want to change (3). i.e., we might want to be able to save a file with a deterministic name but have Spack figure out how it was compressed after the fact (using @adamjstewart: does that make sense? |
|
@tgamblin Makes perfect sense. Thanks for the detailed explanation! |
This is all way too complicated. We can just be explicit about which extension we are looking for on PyPI, just like we do with every other remote repo that we use. And if PyPI doesn't have that extension, it should fail, just like every other remote repo. |
@citibeth: Yes. Maybe it would be simpler to just provide the PyPI filename to version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5', pypi=roundup, filename='roundup-1.1.2.tar.gz')That would give you the information you need, and it might be easier for a user to construct. Can someone fill me in on why using the regular old PyPI URL doesn't work? In the example linked from #2281, |
|
Can someone fill me in on why using the regular old PyPI URL doesn't work?
In the example linked from #2281
<#2281>, http://pypi.python.org/
packages/source/r/roundup/roundup-1.1.2.tar.gz is the URL. Seems like
that isn't so hard to handle with good old url=... don't we just want
special querying for PyPI?
Because in many cases, the required URL has a hash or random string (not
sure which) in it and cannot be constructed in an obvious way from the
package name and version (the above example is a special case). That
breaks `url_for_version()` and a package-level `url=`, and makes users
specify a separate URL for each version manually. Which is really painful,
especially because the resulting URLs are long and look bad.
@citibeth <https://github.com/citibeth>: Yes. Maybe it would be simpler to
just provide the PyPI filename to version() along with the hash and
package name, e.g.:
version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5', pypi=roundup, filename='roundup-1.1.2.tar.gz')
That would give you the information you need, and it might be easier for a
user to construct.
This isn't bad for what it is; but it loses the convenience of having a
single setting (like `url=`) that works automatically for the entire
package. It will feel easier if users don't have to specify all this stuff
for each version.
Also, I'm not sure why `roundup` and the version number need to be
specified twice above.
|
|
This may be better factored, then: version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5', pypi='roundup', extension='tar.gz')I think I like that better anyway; specifying the extension just seems like such a hack. Maybe |
|
On Tue, Jan 3, 2017 at 3:46 PM, Todd Gamblin ***@***.***> wrote:
This may be better factored, then:
version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5', pypi=roundup, extension='tar.gz')
I think I like that better anyway; specifying the extension just seems
like such a hack. Maybe .tar.gz could be the default and we could avoid
it in most cases.
You like that better than:
```
class MyPackage(Package):
url = 'pypi://roundup.tar.gz'
version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5')
```
???
Now the mirror URL can be constructed statically, and the
PyPIFetchStrategy will need to negotiate for the long URL when doing a
fetch directly from PyPI.
Sounds good to me:
|
|
@citibeth: what does the URL mean in your example? The user would have to know how to construct an entirely new URL that no other tool understands. |
|
I agree it's slightly cheesy. The non-cheesy way would be:
```
url='pypi://roundup-1.1.1.tar.gz'
```
This URL would have a well-defined meaning to fetch the PyPI file that is
`package='roundup', version='1.1.1', extension='.tar.gz'`. It would be
easy to create a standalone PyPI downloader that does exactly that.
The fact that other tools don't understand the `pypi://` protocol doesn't
bother me. Some tools understand the `jdbc:oracle//` protocol or `smb://`
protocol, but Spack does not. That doesn't bother me either.
…On Tue, Jan 3, 2017 at 4:18 PM, Todd Gamblin ***@***.***> wrote:
@citibeth <https://github.com/citibeth>: what does the URL mean in your
example? The user would have to know how to construct an entirely new URL
that no other tool understands.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2718 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AB1cdwsIPCN05pqZYFLOXhV3q184Vzs2ks5rOrsIgaJpZM4LZPkv>
.
|
|
@citibeth it bothers me that we're making a URL for PyPI. If PyPI wants to make URLs they can make their own URL and we can use it. But if it's in the URL field, it should have the same semantics as other URLs -- i.e. it should be fetchable by cURL. That one's not. For PyPI we really only need the package name and version, and for mirrors we need the extension. All of those are provided explicitly, without parsing, via the kwargs to class Roundup(Package):
pypi = 'roundup'
extension = 'tar.gz'
version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5')
version('1.1.1', '7c395da56412e263d7600fa7f0afa2e5')But otherwise why invent a new way to specify something when we already have a perfectly good parameter system? |
Can't really argue with that logic. Although that certainly doesn't preclude us from checking for a version that matches a checksum. If it exists, use it. If not, fall back to the previously discussed methods.
That seems like a good compromise to me. Can we default to One thing this solution doesn't solve is that a lot of commands call Either way, I think I could hack Spack to get the |
|
Specifying the extension isn't that much of a hack I guess. We do it elsewhere for packages that don't have an extension ( |
You need at least the
I think this should probably not rely on
I actually don't think this takes as much code as you think. I haven't reviewed the PR but i can take a look sometime this week, after 0.10 goes out (really. really really). I can then try to figure out what needs documenting and/or refactoring. |
That's true for the current implementation, but I don't think it has to be. This could be negotiated. But I'm fine with getting things working the way you propose for now and making them more auto-magic down the road if we can agree on that.
I'm fine with that. There's really no rush on this PR. We have a pretty easy workaround for the problem (specifying |
|
```
class Roundup(Package):
pypi = 'roundup'
extension = '.tar.gz'
version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5')
version('1.1.1', '7c395da56412e263d7600fa7f0afa2e5')
```
OK, LGTM. But my worry is... now that these non-URL schemes are
proliferating, what happens if someone specifies both? For example:
```
class Roundup(Package):
pypi = 'roundup'
git = 'http://github.com/llnl/spack'
url = 'http://myurl/roundup-1.1.1.tar.gz'
extension = '.tar.gz'
version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5')
version('1.1.1', '7c395da56412e263d7600fa7f0afa2e5')
```
Or what about this?
```
class Roundup(Package):
pypi = 'roundup'
extension = '.tar.gz'
version('1.1.2', '7c395da56412e263d7600fa7f0afa2e5', git='....')
version('1.1.1', '7c395da56412e263d7600fa7f0afa2e5')
```
My general feeling is:
1. It should be illegal to specify more than one fetch type at the class
level.
2. Due to (1), maybe we should be saying something like `fetch=(pypi,
'roundup')` or `fetch=pypi` or `fetch=(git, 'git://....')` at the class
level --- a multi-valued variant idea.
3. Specifications at the `version` level should wipe out and completely
override any specifications at the class level. So... if you specify
`pypi` at the class level and `git` at the version level, all PyPI-related
stuff at the class level will be ignore (so as to not trigger error
checking in (1)).
Can we default to pypi = self.name[3:] and extension = '.tar.gz'?
You need at least the pypi attribute in there to indicate that this
should be fetched from PyPI. If the default for pypi is set in the
PythonPackage class, that should be ok, but it has to have pypi someplace.
Not all Python packages (meaning, packages built with `setup.py`) install
from PyPI. And of those that do, sometimes we want to install from a
location other than PyPI. So I don't think that `PythonPackage` should
default `pypi`, otherwise how would we create a `PythonPackage` NOT from
PyPI (in an obvious, straightforward way)? Maybe `PyPIPackage` can, if we
want that superclass. But again... if we don't want to tie our hashes to
PyPI, why would we tie our naming sheme to PyPI?
|
|
@citibeth I agree that you shouldn't be able to specify multiple fetching schemes at the class level. As for how to enforce that, I'll leave it up to @tgamblin's refactoring. But you ought to be able to do something like this: class Numpy(Package):
url = 'url'
version('1.2.3', 'hash')
version('1.2.4', 'hash', pypi='name', extension='.tar.gz')
version('1.2.5', 'hash', url='url')
version('1.2.6', 'hash', git='url', branch='branch')In this case, the package-default is to use a URL. But a version-specific fetching scheme overrides this. Version fetching schemes have a higher priority than package fetching schemes, just like they do now if you specify multiple URLs. Except, wait, wtf is going on in #2725? Looks like this broke recently. |
|
Abandoning this PR as it is obsolete. If you follow https://wiki.python.org/moin/PyPIXmlRpc you'll find that:
Documentation on the modern JSON APIs can be found at https://warehouse.readthedocs.io/api-reference/json/. I couldn't figure out how to use it, but if someone shows me how I can try to revive this PR. |
Resolves #2281.
This PR is pretty hacky at the moment, so it will need a lot of work before it is properly integrated. But it is a totally working proof of concept:
In the end, I'm hoping to support the following modes:
1.
pypipassed to the version directivePackages should be able to have multiple versions, some that download from git and some that download from PyPi. The following should work:
2.
pypideclared at the package levelIf you have ten versions, you don't want to add
pypi='name'to every single version directive. You should be able to declare it once at the package level.3. packages named
py-*without a URL?If a URL isn't provided and the package starts with
py-, why not callPyPiFetchStrategy?4. packages that subclass
PythonPackagewithout a URL?Same as above. See #2709