Skip to content

Commit 537acec

Browse files
committed
Support PEP 658
1 parent 2723a6a commit 537acec

File tree

11 files changed

+283
-24
lines changed

11 files changed

+283
-24
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v0.9.0 (in development)
2+
-----------------------
3+
- Support PEP 658 by adding `has_metadata`, `metadata_url`, and
4+
`metadata_digests` attributes to `DistributionPackage`
5+
16
v0.8.0 (2020-12-13)
27
-------------------
38
- Support Python 3.9

README.rst

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@
2424
| `Changelog <https://github.com/jwodder/pypi-simple/blob/master/CHANGELOG.md>`_
2525
2626
``pypi-simple`` is a client library for the Python Simple Repository API as
27-
specified in :pep:`503` and updated by :pep:`592` and :pep:`629`. With it, you
28-
can query `the Python Package Index (PyPI) <https://pypi.org>`_ and other `pip
29-
<https://pip.pypa.io>`_-compatible repositories for a list of their available
30-
projects and lists of each project's available package files. The library also
31-
allows you to query package files for their project version, package type, file
32-
digests, ``requires_python`` string, and PGP signature URL.
27+
specified in :pep:`503` and updated by :pep:`592`, :pep:`629`, and :pep:`658`.
28+
With it, you can query `the Python Package Index (PyPI) <https://pypi.org>`_
29+
and other `pip <https://pip.pypa.io>`_-compatible repositories for a list of
30+
their available projects and lists of each project's available package files.
31+
The library also allows you to query package files for their project version,
32+
package type, file digests, ``requires_python`` string, PGP signature URL, and
33+
metadata URL.
3334

3435
See `the documentation <https://pypi-simple.readthedocs.io>`_ for more
3536
information.
@@ -52,7 +53,7 @@ Example
5253
... requests_page = client.get_project_page('requests')
5354
>>> pkg = requests_page.packages[0]
5455
>>> pkg
55-
DistributionPackage(filename='requests-0.2.0.tar.gz', url='https://files.pythonhosted.org/packages/ba/bb/dfa0141a32d773c47e4dede1a617c59a23b74dd302e449cf85413fc96bc4/requests-0.2.0.tar.gz#sha256=813202ace4d9301a3c00740c700e012fb9f3f8c73ddcfe02ab558a8df6f175fd', project='requests', version='0.2.0', package_type='sdist', requires_python=None, has_sig=None, yanked=None)
56+
DistributionPackage(filename='requests-0.2.0.tar.gz', url='https://files.pythonhosted.org/packages/ba/bb/dfa0141a32d773c47e4dede1a617c59a23b74dd302e449cf85413fc96bc4/requests-0.2.0.tar.gz#sha256=813202ace4d9301a3c00740c700e012fb9f3f8c73ddcfe02ab558a8df6f175fd', project='requests', version='0.2.0', package_type='sdist', requires_python=None, has_sig=None, yanked=None, metadata_digests=None)
5657
>>> pkg.filename
5758
'requests-0.2.0.tar.gz'
5859
>>> pkg.url

docs/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
Changelog
44
=========
55

6+
v0.9.0 (in development)
7+
-----------------------
8+
- Support :pep:`658` by adding `~DistributionPackage.has_metadata`,
9+
`~DistributionPackage.metadata_url`, and
10+
`~DistributionPackage.metadata_digests` attributes to `DistributionPackage`
11+
12+
613
v0.8.0 (2020-12-13)
714
-------------------
815
- Support Python 3.9

docs/index.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ pypi-simple — PyPI Simple Repository API client library
1818
changelog
1919

2020
``pypi-simple`` is a client library for the Python Simple Repository API as
21-
specified in :pep:`503` and updated by :pep:`592` and :pep:`629`. With it, you
22-
can query `the Python Package Index (PyPI) <https://pypi.org>`_ and other `pip
23-
<https://pip.pypa.io>`_-compatible repositories for a list of their available
24-
projects and lists of each project's available package files. The library also
25-
allows you to query package files for their project version, package type, file
26-
digests, ``requires_python`` string, and PGP signature URL.
27-
21+
specified in :pep:`503` and updated by :pep:`592`, :pep:`629`, and :pep:`658`.
22+
With it, you can query `the Python Package Index (PyPI) <https://pypi.org>`_
23+
and other `pip <https://pip.pypa.io>`_-compatible repositories for a list of
24+
their available projects and lists of each project's available package files.
25+
The library also allows you to query package files for their project version,
26+
package type, file digests, ``requires_python`` string, PGP signature URL, and
27+
metadata URL.
2828

2929
Installation
3030
============
@@ -36,14 +36,14 @@ Installation
3636

3737

3838
Example
39-
========
39+
=======
4040

4141
>>> from pypi_simple import PyPISimple
4242
>>> with PyPISimple() as client:
4343
... requests_page = client.get_project_page('requests')
4444
>>> pkg = requests_page.packages[0]
4545
>>> pkg
46-
DistributionPackage(filename='requests-0.2.0.tar.gz', url='https://files.pythonhosted.org/packages/ba/bb/dfa0141a32d773c47e4dede1a617c59a23b74dd302e449cf85413fc96bc4/requests-0.2.0.tar.gz#sha256=813202ace4d9301a3c00740c700e012fb9f3f8c73ddcfe02ab558a8df6f175fd', project='requests', version='0.2.0', package_type='sdist', requires_python=None, has_sig=None, yanked=None)
46+
DistributionPackage(filename='requests-0.2.0.tar.gz', url='https://files.pythonhosted.org/packages/ba/bb/dfa0141a32d773c47e4dede1a617c59a23b74dd302e449cf85413fc96bc4/requests-0.2.0.tar.gz#sha256=813202ace4d9301a3c00740c700e012fb9f3f8c73ddcfe02ab558a8df6f175fd', project='requests', version='0.2.0', package_type='sdist', requires_python=None, has_sig=None, yanked=None, metadata_digests=None)
4747
>>> pkg.filename
4848
'requests-0.2.0.tar.gz'
4949
>>> pkg.url

src/pypi_simple/__init__.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22
PyPI Simple Repository API client library
33
44
``pypi-simple`` is a client library for the Python Simple Repository API as
5-
specified in :pep:`503` and updated by :pep:`592` and :pep:`629`. With it, you
6-
can query `the Python Package Index (PyPI) <https://pypi.org>`_ and other `pip
7-
<https://pip.pypa.io>`_-compatible repositories for a list of their available
8-
projects and lists of each project's available package files. The library also
9-
allows you to query package files for their project version, package type, file
10-
digests, ``requires_python`` string, and PGP signature URL.
5+
specified in :pep:`503` and updated by :pep:`592`, :pep:`629`, and :pep:`658`.
6+
With it, you can query `the Python Package Index (PyPI) <https://pypi.org>`_
7+
and other `pip <https://pip.pypa.io>`_-compatible repositories for a list of
8+
their available projects and lists of each project's available package files.
9+
The library also allows you to query package files for their project version,
10+
package type, file digests, ``requires_python`` string, PGP signature URL, and
11+
metadata URL.
1112
1213
Visit <https://github.com/jwodder/pypi-simple> or <https://pypi-simple.rtfd.io>
1314
for more information.
1415
"""
1516

16-
__version__ = "0.8.0"
17+
__version__ = "0.9.0.dev1"
1718
__author__ = "John Thorvald Wodder II"
1819
__author_email__ = "[email protected]"
1920
__license__ = "MIT"

src/pypi_simple/classes.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from typing import Dict, List, NamedTuple, Optional, Union
23
from urllib.parse import urlparse, urlunparse
34
from .filenames import parse_filename
@@ -34,6 +35,9 @@ class DistributionPackage(NamedTuple):
3435
3536
.. versionchanged:: 0.5.0
3637
`yanked` attribute added
38+
39+
.. versionchanged:: 0.9.0
40+
`has_metadata`, `metadata_url`, and `metadata_digests` attributes added
3741
"""
3842

3943
#: The basename of the package file
@@ -80,6 +84,12 @@ class DistributionPackage(NamedTuple):
8084
#: yanked; otherwise, it is `None`.
8185
yanked: Optional[str]
8286

87+
#: If the package repository provides a Core Metadata file for the package,
88+
#: this is a (possibly empty) `dict` of digests of the file, given as a
89+
#: mapping from hash algorithm names to hex-encoded digest strings;
90+
#: otherwise, it is `None`
91+
metadata_digests: Optional[Dict[str, str]]
92+
8393
@property
8494
def sig_url(self) -> str:
8595
"""
@@ -93,6 +103,23 @@ def sig_url(self) -> str:
93103
u = urlparse(self.url)
94104
return urlunparse((u[0], u[1], u[2] + ".asc", "", "", ""))
95105

106+
@property
107+
def has_metadata(self) -> bool:
108+
"""Whether the package file is accompanied by a Core Metadata file"""
109+
return self.metadata_digests is not None
110+
111+
@property
112+
def metadata_url(self) -> Optional[str]:
113+
"""
114+
If the package repository provides a Core Metadata file for the
115+
package, this is the URL for that file; otherwise, it is `None`.
116+
"""
117+
if self.has_metadata:
118+
u = urlparse(self.url)
119+
return urlunparse((u[0], u[1], u[2] + ".metadata", "", "", ""))
120+
else:
121+
return None
122+
96123
def get_digests(self) -> Dict[str, str]:
97124
"""
98125
Extracts the hash digests from the package file's URL and returns a
@@ -130,6 +157,15 @@ def get_str_attrib(attrib: str) -> Optional[str]:
130157
has_sig = gpg_sig.lower() == "true"
131158
else:
132159
has_sig = None
160+
mddigest = get_str_attrib("data-dist-info-metadata")
161+
metadata_digests: Optional[Dict[str, str]]
162+
if mddigest is not None:
163+
metadata_digests = {}
164+
m = re.fullmatch(r"(\w+)=([0-9A-Fa-f]+)", mddigest)
165+
if m:
166+
metadata_digests[m[1]] = m[2]
167+
else:
168+
metadata_digests = None
133169
return cls(
134170
filename=link.text,
135171
url=link.url,
@@ -139,6 +175,7 @@ def get_str_attrib(attrib: str) -> Optional[str]:
139175
version=version,
140176
package_type=pkg_type,
141177
yanked=get_str_attrib("data-yanked"),
178+
metadata_digests=metadata_digests,
142179
)
143180

144181

test/deprecated/test_parse_project_page.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def test_parse_qypi():
3030
requires_python="~=3.4",
3131
has_sig=None,
3232
yanked=None,
33+
metadata_digests=None,
3334
),
3435
DistributionPackage(
3536
filename="qypi-0.1.0.tar.gz",
@@ -40,6 +41,7 @@ def test_parse_qypi():
4041
requires_python="~=3.4",
4142
has_sig=None,
4243
yanked=None,
44+
metadata_digests=None,
4345
),
4446
DistributionPackage(
4547
filename="qypi-0.1.0.post1-py3-none-any.whl",
@@ -50,6 +52,7 @@ def test_parse_qypi():
5052
requires_python="~=3.4",
5153
has_sig=None,
5254
yanked=None,
55+
metadata_digests=None,
5356
),
5457
DistributionPackage(
5558
filename="qypi-0.1.0.post1.tar.gz",
@@ -60,6 +63,7 @@ def test_parse_qypi():
6063
requires_python="~=3.4",
6164
has_sig=None,
6265
yanked=None,
66+
metadata_digests=None,
6367
),
6468
DistributionPackage(
6569
filename="qypi-0.2.0-py3-none-any.whl",
@@ -70,6 +74,7 @@ def test_parse_qypi():
7074
requires_python="~=3.4",
7175
has_sig=None,
7276
yanked=None,
77+
metadata_digests=None,
7378
),
7479
DistributionPackage(
7580
filename="qypi-0.2.0.tar.gz",
@@ -80,6 +85,7 @@ def test_parse_qypi():
8085
requires_python="~=3.4",
8186
has_sig=None,
8287
yanked=None,
88+
metadata_digests=None,
8389
),
8490
DistributionPackage(
8591
filename="qypi-0.3.0-py3-none-any.whl",
@@ -90,6 +96,7 @@ def test_parse_qypi():
9096
requires_python="~=3.4",
9197
has_sig=None,
9298
yanked=None,
99+
metadata_digests=None,
93100
),
94101
DistributionPackage(
95102
filename="qypi-0.3.0.tar.gz",
@@ -100,6 +107,7 @@ def test_parse_qypi():
100107
requires_python="~=3.4",
101108
has_sig=None,
102109
yanked=None,
110+
metadata_digests=None,
103111
),
104112
DistributionPackage(
105113
filename="qypi-0.4.0-py3-none-any.whl",
@@ -110,6 +118,7 @@ def test_parse_qypi():
110118
requires_python="~=3.4",
111119
has_sig=None,
112120
yanked=None,
121+
metadata_digests=None,
113122
),
114123
DistributionPackage(
115124
filename="qypi-0.4.0.tar.gz",
@@ -120,6 +129,7 @@ def test_parse_qypi():
120129
requires_python="~=3.4",
121130
has_sig=None,
122131
yanked=None,
132+
metadata_digests=None,
123133
),
124134
DistributionPackage(
125135
filename="qypi-0.4.1-py3-none-any.whl",
@@ -130,6 +140,7 @@ def test_parse_qypi():
130140
requires_python="~=3.4",
131141
has_sig=None,
132142
yanked=None,
143+
metadata_digests=None,
133144
),
134145
DistributionPackage(
135146
filename="qypi-0.4.1.tar.gz",
@@ -140,6 +151,7 @@ def test_parse_qypi():
140151
requires_python="~=3.4",
141152
has_sig=None,
142153
yanked=None,
154+
metadata_digests=None,
143155
),
144156
]
145157

@@ -162,6 +174,7 @@ def test_parse_qypi_base():
162174
requires_python="~=3.4",
163175
has_sig=None,
164176
yanked=None,
177+
metadata_digests=None,
165178
),
166179
DistributionPackage(
167180
filename="qypi-0.1.0.tar.gz",
@@ -172,6 +185,7 @@ def test_parse_qypi_base():
172185
requires_python="~=3.4",
173186
has_sig=None,
174187
yanked=None,
188+
metadata_digests=None,
175189
),
176190
DistributionPackage(
177191
filename="qypi-0.1.0.post1-py3-none-any.whl",
@@ -182,6 +196,7 @@ def test_parse_qypi_base():
182196
requires_python="~=3.4",
183197
has_sig=None,
184198
yanked=None,
199+
metadata_digests=None,
185200
),
186201
DistributionPackage(
187202
filename="qypi-0.1.0.post1.tar.gz",
@@ -192,6 +207,7 @@ def test_parse_qypi_base():
192207
requires_python="~=3.4",
193208
has_sig=None,
194209
yanked=None,
210+
metadata_digests=None,
195211
),
196212
]
197213

@@ -214,6 +230,7 @@ def test_parse_qypi_mixed():
214230
requires_python=None,
215231
has_sig=None,
216232
yanked="Metadata was smelly",
233+
metadata_digests=None,
217234
),
218235
DistributionPackage(
219236
filename="qypi-0.1.0.tar.gz",
@@ -224,6 +241,7 @@ def test_parse_qypi_mixed():
224241
requires_python="~=3.4",
225242
has_sig=None,
226243
yanked="",
244+
metadata_digests=None,
227245
),
228246
DistributionPackage(
229247
filename="qypi-0.1.0.post1-py3-none-any.whl",
@@ -234,6 +252,7 @@ def test_parse_qypi_mixed():
234252
requires_python=None,
235253
has_sig=True,
236254
yanked="",
255+
metadata_digests=None,
237256
),
238257
DistributionPackage(
239258
filename="qypi-0.1.0.post1.tar.gz",
@@ -244,6 +263,7 @@ def test_parse_qypi_mixed():
244263
requires_python="~=3.4",
245264
has_sig=True,
246265
yanked=None,
266+
metadata_digests=None,
247267
),
248268
DistributionPackage(
249269
filename="qypi-0.2.0-py3-none-any.whl",
@@ -254,6 +274,7 @@ def test_parse_qypi_mixed():
254274
requires_python=None,
255275
has_sig=False,
256276
yanked=None,
277+
metadata_digests=None,
257278
),
258279
]
259280

@@ -276,6 +297,7 @@ def test_parse_devpi_devpi():
276297
requires_python=None,
277298
has_sig=None,
278299
yanked=None,
300+
metadata_digests=None,
279301
),
280302
DistributionPackage(
281303
filename="devpi-2.1.0.tar.gz",
@@ -286,6 +308,7 @@ def test_parse_devpi_devpi():
286308
requires_python=None,
287309
has_sig=None,
288310
yanked=None,
311+
metadata_digests=None,
289312
),
290313
DistributionPackage(
291314
filename="devpi-2.0.3.tar.gz",
@@ -296,6 +319,7 @@ def test_parse_devpi_devpi():
296319
requires_python=None,
297320
has_sig=None,
298321
yanked=None,
322+
metadata_digests=None,
299323
),
300324
DistributionPackage(
301325
filename="devpi-2.0.2.tar.gz",
@@ -306,6 +330,7 @@ def test_parse_devpi_devpi():
306330
requires_python=None,
307331
has_sig=None,
308332
yanked=None,
333+
metadata_digests=None,
309334
),
310335
DistributionPackage(
311336
filename="devpi-2.0.1.tar.gz",
@@ -316,5 +341,6 @@ def test_parse_devpi_devpi():
316341
requires_python=None,
317342
has_sig=None,
318343
yanked=None,
344+
metadata_digests=None,
319345
),
320346
]

0 commit comments

Comments
 (0)