@@ -37,29 +37,40 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None
3737
3838@dataclasses .dataclass (frozen = True )
3939class 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