@@ -37,29 +37,40 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None
37
37
38
38
@dataclasses .dataclass (frozen = True )
39
39
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."""
42
41
pypi_url : str
43
42
44
43
async def files_for_package (self , package_name : str ) -> dict [str , str ]:
45
44
async with httpx .AsyncClient () as client :
46
45
resp = await client .get (
47
46
f'{ self .pypi_url } /{ package_name } ' ,
48
47
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
+ },
49
55
)
50
56
if resp .status_code == 404 :
51
57
raise PackageDoesNotExist (package_name )
52
- parser = HTMLAnchorParser ()
53
- parser .feed (resp .text )
54
58
55
59
def clean_url (url : str ) -> str :
56
60
parsed = urllib .parse .urlparse (urllib .parse .urljoin (str (resp .url ), url ))
57
61
return parsed ._replace (fragment = '' ).geturl ()
58
62
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
+ }
63
74
64
75
65
76
@dataclasses .dataclass (frozen = True )
0 commit comments