Skip to content

Commit 5b3ab8e

Browse files
committed
Add PEP691 support
1 parent 7a36c26 commit 5b3ab8e

File tree

2 files changed

+32
-15
lines changed

2 files changed

+32
-15
lines changed

README.md

+13-7
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,21 @@ You can set these environment variables to configure the server:
5757
* `PYPI_BROWSER_PYPI_URL`: URL for the PyPI server to use (defaults to
5858
`https://pypi.org`)
5959

60-
If your registry supports the pypi.org-compatible JSON API (e.g.
61-
`{registry}/pypi/{package}/json`), specify your base registry URL without
62-
appending `/simple` (e.g. `https://my-registry`).
60+
For best results (i.e. most available metadata), choose the first option from
61+
this list which is supported by your PyPI registry:
6362

64-
If your registry only supports the traditional HTML "simple" index, specify
65-
the registry URL with `/simple` at the end (e.g.
66-
`https://my-registry/simple`).
63+
* If your registry supports [PEP691](pep691) JSON "simple" indexes, specify
64+
your base registry URL with `/simple` appended (e.g.
65+
`https://my-registry/simple`).
66+
67+
* If your registry supports the legacy pypi.org-compatible JSON API (e.g.
68+
`{registry}/pypi/{package}/json`), specify your base registry URL without
69+
a suffix (e.g. `https://my-registry`).
70+
71+
* Otherwise, if your registry only supports the traditional HTML "simple"
72+
index, specify the registry URL with `/simple` at the end (e.g.
73+
`https://my-registry/simple`).
6774

68-
Note that the [PEP691][pep691] JSON-based "simple" API is not yet supported.
6975

7076
* `PYPI_BROWSER_PACKAGE_CACHE_PATH`: Filesystem path to use for caching
7177
downloaded files. This will grow forever (the app does not clean it up) so

pypi_browser/pypi.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,40 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None
3737

3838
@dataclasses.dataclass(frozen=True)
3939
class SimpleRepository(PythonRepository):
40-
"""Old-style "simple" PyPI registry serving HTML files."""
41-
# TODO: Also handle PEP691 JSON simple repositories.
40+
""""Simple" PyPI registry serving either JSON or HTML files."""
4241
pypi_url: str
4342

4443
async def files_for_package(self, package_name: str) -> dict[str, str]:
4544
async with httpx.AsyncClient() as client:
4645
resp = await client.get(
4746
f'{self.pypi_url}/{package_name}',
4847
follow_redirects=True,
48+
headers={
49+
'Accept': ', '.join((
50+
'application/vnd.pypi.simple.v1+json',
51+
'application/vnd.pypi.simple.v1+html;q=0.2',
52+
'text/html;q=0.01',
53+
)),
54+
},
4955
)
5056
if resp.status_code == 404:
5157
raise PackageDoesNotExist(package_name)
52-
parser = HTMLAnchorParser()
53-
parser.feed(resp.text)
5458

5559
def clean_url(url: str) -> str:
5660
parsed = urllib.parse.urlparse(urllib.parse.urljoin(str(resp.url), url))
5761
return parsed._replace(fragment='').geturl()
5862

59-
return {
60-
(urllib.parse.urlparse(url).path).split('/')[-1]: clean_url(url)
61-
for url in parser.anchors
62-
}
63+
if resp.headers.get('Content-Type') == 'application/vnd.pypi.simple.v1+json':
64+
result = resp.json()
65+
return {file_['filename']: clean_url(file_['url']) for file_ in result['files']}
66+
else:
67+
parser = HTMLAnchorParser()
68+
parser.feed(resp.text)
69+
70+
return {
71+
(urllib.parse.urlparse(url).path).split('/')[-1]: clean_url(url)
72+
for url in parser.anchors
73+
}
6374

6475

6576
@dataclasses.dataclass(frozen=True)

0 commit comments

Comments
 (0)