-
Notifications
You must be signed in to change notification settings - Fork 325
Tighten and simplify extraction of metadata from wheels #1201
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,68 +14,48 @@ | |
| import os | ||
| import re | ||
| import zipfile | ||
| from typing import List | ||
| from contextlib import suppress | ||
|
|
||
| from twine import distribution | ||
| from twine import exceptions | ||
|
|
||
| wheel_file_re = re.compile( | ||
| r"""^(?P<namever>(?P<name>.+?)(-(?P<ver>\d.+?))?) | ||
| ((-(?P<build>\d.*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?) | ||
| \.whl|\.dist-info)$""", | ||
| r"""^(?P<name>[^-]+)- | ||
| (?P<version>[^-]+) | ||
| (:?-(?P<build>\d[^-]*))?- | ||
| (?P<pyver>[^-]+)- | ||
| (?P<abi>[^-]+)- | ||
| (?P<plat>[^-]+) | ||
| \.whl$""", | ||
| re.VERBOSE, | ||
| ) | ||
|
|
||
|
|
||
| class Wheel(distribution.Distribution): | ||
| def __init__(self, filename: str) -> None: | ||
| m = wheel_file_re.match(os.path.basename(filename)) | ||
| if not m: | ||
| raise exceptions.InvalidDistribution(f"Invalid wheel filename: {filename}") | ||
| self.name = m.group("name") | ||
| self.version = m.group("version") | ||
| self.pyver = m.group("pyver") | ||
|
|
||
| if not os.path.exists(filename): | ||
| raise exceptions.InvalidDistribution(f"No such file: {filename}") | ||
| self.filename = filename | ||
|
|
||
| @property | ||
| def py_version(self) -> str: | ||
| wheel_info = wheel_file_re.match(os.path.basename(self.filename)) | ||
| if wheel_info is None: | ||
| return "any" | ||
| else: | ||
| return wheel_info.group("pyver") | ||
|
|
||
| @staticmethod | ||
| def find_candidate_metadata_files(names: List[str]) -> List[List[str]]: | ||
| """Filter files that may be METADATA files.""" | ||
| tuples = [x.split("/") for x in names if "METADATA" in x] | ||
| return [x[1] for x in sorted((len(x), x) for x in tuples)] | ||
| return self.pyver | ||
|
|
||
| def read(self) -> bytes: | ||
| fqn = os.path.abspath(os.path.normpath(self.filename)) | ||
| if not os.path.exists(fqn): | ||
| raise exceptions.InvalidDistribution("No such file: %s" % fqn) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're also losing the clarity from this exception if the give file/path does not exist? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same check existed in the class constructor and in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now the |
||
|
|
||
| if fqn.endswith(".whl"): | ||
| archive = zipfile.ZipFile(fqn) | ||
| names = archive.namelist() | ||
|
|
||
| def read_file(name: str) -> bytes: | ||
| return archive.read(name) | ||
|
|
||
| else: | ||
| raise exceptions.InvalidDistribution( | ||
| "Not a known archive format for file: %s" % fqn | ||
| ) | ||
|
|
||
| searched_files: List[str] = [] | ||
| try: | ||
| for path in self.find_candidate_metadata_files(names): | ||
| candidate = "/".join(path) | ||
| data = read_file(candidate) | ||
| with zipfile.ZipFile(self.filename) as wheel: | ||
| with suppress(KeyError): | ||
| # The wheel needs to contain the METADATA file at this location. | ||
| data = wheel.read(f"{self.name}-{self.version}.dist-info/METADATA") | ||
| if b"Metadata-Version" in data: | ||
| return data | ||
| searched_files.append(candidate) | ||
| finally: | ||
| archive.close() | ||
|
|
||
| raise exceptions.InvalidDistribution( | ||
| "No METADATA in archive or METADATA missing 'Metadata-Version': " | ||
| "%s (searched %s)" % (fqn, ",".join(searched_files)) | ||
| "No METADATA in archive or " | ||
| f"METADATA missing 'Metadata-Version': {self.filename}" | ||
| ) | ||
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 seems we lose this if we just accept filename as passed into the Wheel object, no?
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 is intended. First,
os.path.abspath(path)is equivalent toos.path.normpath(os.getcwd(), path)thus normalizing withnormpath()before callingabspath()is redundant. Then,normpath()operates only on the representation of the path and does funny things with symbolic links. Most likely corner cases, but making corner cases more confusing is not making them easier to diagnose. Third, I like the error messages to refer to the path as provided by the user and not to a "normalized" path.TL;DR: I didn't understand why this was a good idea.