Skip to content

Commit c018f96

Browse files
committed
Merge branch 'mr/cardao/update-PyPILink' into 'master'
PyPILink: retrieve filename and requires-python data See merge request it/e3-core!68
2 parents cad76e0 + f9021a0 commit c018f96

File tree

1 file changed

+71
-35
lines changed

1 file changed

+71
-35
lines changed

src/e3/python/pypi.py

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,54 @@ def get_pip_env(platform: str, python_version: Version) -> dict[str, str]:
7979
class PyPILink:
8080
"""Link returned by PyPI simple API."""
8181

82-
def __init__(self, url: str, yanked: str | None, has_metadata: bool) -> None:
82+
def __init__(
83+
self,
84+
identifier: str,
85+
url: str,
86+
yanked: str | None,
87+
has_metadata: bool,
88+
require_python: str | None = None,
89+
) -> None:
8390
"""Initialize a PyPI link.
8491
92+
:param identifier: the project identifier
8593
:param url: url of the resource
8694
:param yanked: yanker data
8795
:param has_metadata: True if metadata is directly available from PyPI
96+
:param require_python: require python data
8897
"""
98+
self.identifier = identifier
8999
self.url = url
90100
self.yanked = yanked
101+
self.require_python = require_python
91102
self.has_metadata = has_metadata
103+
self._urlparse = urlparse(url)
104+
self.checksum = self._urlparse.fragment.replace("sha256=", "", 1)
105+
self.filename = self._urlparse.path.rpartition("/")[-1]
106+
107+
py_tags = ""
108+
abi_tags = ""
109+
platform_tags = ""
110+
# Retreive a package version.
111+
if self.filename.endswith(".whl"):
112+
# Wheel filenames contain compatibility information
113+
_, version, py_tags, abi_tags, platform_tags = self.filename[:-4].rsplit(
114+
"-", 4
115+
)
116+
else:
117+
package_filename = self.filename
118+
if any(package_filename.endswith(ext) for ext in (".tar.gz", ".tar.bz2")):
119+
# Remove .gz or .bz2
120+
package_filename, _ = os.path.splitext(package_filename)
121+
# Remove remaining extension
122+
basename, ext = os.path.splitext(package_filename)
123+
# Retrieve version
124+
_, version, *_ = basename.rsplit("-", 1)
125+
126+
self.pkg_version = Version(version)
127+
self.pkg_py_tags = py_tags.split(".")
128+
self.pkg_abi_tags = abi_tags.split(".")
129+
self.pkg_platform_tags = platform_tags.split(".")
92130

93131
@property
94132
def is_yanked(self) -> bool:
@@ -102,11 +140,17 @@ def metadata_url(self) -> str:
102140

103141
def as_dict(self) -> dict[str, None | bool | str]:
104142
"""Serialize the a PyPILink into a Python dict that can be dump as json."""
105-
return {
143+
res = {
144+
"identifier": self.identifier,
106145
"url": self.url,
146+
"filename": self.filename,
147+
"checksum": self.checksum,
107148
"yanked": self.yanked,
108149
"has_metadata": self.has_metadata,
109150
}
151+
if self.require_python:
152+
res["require_python"] = self.require_python
153+
return res
110154

111155
@classmethod
112156
def from_dict(cls, data: dict) -> PyPILink:
@@ -115,30 +159,40 @@ def from_dict(cls, data: dict) -> PyPILink:
115159
:param data: the dict to read
116160
"""
117161
return PyPILink(
118-
url=data["url"], yanked=data["yanked"], has_metadata=data["has_metadata"]
162+
identifier=data["identifier"],
163+
url=data["url"],
164+
yanked=data.get("yanked"),
165+
has_metadata=data["has_metadata"],
166+
require_python=data.get("require-python"),
119167
)
120168

121169

122170
class PyPILinksParser(HTMLParser):
123171
"""HTML parser to parse links from the PyPI simple API."""
124172

125-
def __init__(self) -> None:
173+
def __init__(self, identifier: str) -> None:
126174
"""Initialize the parser."""
127175
super().__init__()
176+
self.identifier = identifier
128177
self.links: list[PyPILink] = []
129178

130179
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
131180
"""See HTMLParser doc."""
132181
if tag == "a":
133182
attr_dict = dict(attrs)
134183
assert attr_dict["href"] is not None
135-
self.links.append(
136-
PyPILink(
137-
url=attr_dict["href"],
138-
yanked=attr_dict.get("data-yanked"),
139-
has_metadata="data-dist-info-metadata" in attr_dict,
184+
try:
185+
self.links.append(
186+
PyPILink(
187+
url=attr_dict["href"],
188+
yanked=attr_dict.get("data-yanked"),
189+
require_python=attr_dict.get("data-requires-python"),
190+
has_metadata="data-dist-info-metadata" in attr_dict,
191+
identifier=self.identifier,
192+
)
140193
)
141-
)
194+
except InvalidVersion:
195+
pass
142196

143197

144198
class PyPI:
@@ -194,7 +248,7 @@ def fetch_project_links(self, name: str) -> list[PyPILink]:
194248
logger.debug(f"fetch {identifier} links from {self.pypi_url}")
195249
pypi_request = requests.get(self.pypi_url + "simple/" + identifier + "/")
196250
pypi_request.raise_for_status()
197-
pypi_links_parser = PyPILinksParser()
251+
pypi_links_parser = PyPILinksParser(identifier)
198252
pypi_links_parser.feed(pypi_request.text)
199253

200254
# Update cache
@@ -282,31 +336,12 @@ def __init__(
282336
self.cache_dir = os.path.abspath(cache_dir)
283337

284338
# Compute filename and extract compatibility information
285-
path = urlparse(self.url).path
286-
self.filename = path.rpartition("/")[-1]
287-
288-
py_tags = ""
289-
abi_tags = ""
290-
platform_tags = ""
291-
292-
if self.filename.endswith(".whl"):
293-
# Wheel filenames contain compatibility information
294-
tmp, py_tags, abi_tags, platform_tags = self.filename[:-4].rsplit("-", 3)
295-
_, version = tmp.split("-", 1)
296-
297-
elif self.filename.endswith(".tar.gz"):
298-
# For sources, packages naming scheme has changed other time
299-
# (mainly handling of - and _) but we expect the format of
300-
# the package to be: <STR_WITH_SAME_LENGTH_AS_NAME>-<VERSION>.tar.gz
301-
version = self.filename[len(self.name) + 1 : -7]
302-
else:
303-
basename, ext = os.path.splitext(self.filename)
304-
name, version = basename.split("-")[:2]
339+
self.filename = link.filename
305340

306-
self.version = Version(version)
307-
self.py_tags = py_tags.split(".")
308-
self.abi_tags = abi_tags.split(".")
309-
self.platform_tags = platform_tags.split(".")
341+
self.version = link.pkg_version
342+
self.py_tags = link.pkg_py_tags
343+
self.abi_tags = link.pkg_abi_tags
344+
self.platform_tags = link.pkg_platform_tags
310345

311346
# Requirements cache
312347
self._reqs: None | set[Requirement] = None
@@ -632,6 +667,7 @@ def add_wheel(self, filename: str) -> None:
632667
name = os.path.basename(filename)[:-4].split("-")[0]
633668
self.pypi.cache[canonicalize_name(name)] = [
634669
PyPILink(
670+
identifier=name,
635671
url=f"file://{os.path.abspath(filename)}".replace("\\", "/"),
636672
yanked=None,
637673
has_metadata=False,

0 commit comments

Comments
 (0)