10
10
11
11
from requests import Request , Session , hooks
12
12
from requests .adapters import HTTPAdapter
13
+ from requests .exceptions import RequestException
13
14
from requests .packages .urllib3 .util .retry import Retry
14
15
15
16
from .constants import PYPI_BASE_URL
@@ -23,7 +24,7 @@ class WoppResponse:
23
24
A structured wrapper for PyPI JSON API responses.
24
25
"""
25
26
26
- def __init__ (self , status_code : int , json : dict [str , Any ]) -> None :
27
+ def __init__ (self , status_code : int , json : dict [str , Any ] | None ) -> None :
27
28
self .status_code : int = status_code
28
29
self .json : dict [str , Any ] = json or {}
29
30
self ._cache : dict [str , Any ] = {}
@@ -113,21 +114,14 @@ def get_releases_with_dates(self) -> list[tuple[str, datetime]]:
113
114
releases_with_dates = []
114
115
for release in self .releases :
115
116
info = self .release_data .get (release , {})
116
- release_date = None
117
117
if info :
118
- # loop through package types to find the first valid upload time
119
- for metadata in info .values ():
120
- upload_time = metadata .get ("upload_time" )
121
- if upload_time :
122
- try :
123
- release_date = datetime .fromisoformat (
124
- upload_time .replace ("Z" , "+00:00" )
125
- )
126
- break
127
- except ValueError :
128
- continue
129
- if release_date :
130
- releases_with_dates .append ((release , release_date ))
118
+ upload_time = info .get ("upload_time" )
119
+ if isinstance (upload_time , str ):
120
+ try :
121
+ release_date = datetime .fromisoformat (upload_time .replace ("Z" , "+00:00" ))
122
+ releases_with_dates .append ((release , release_date ))
123
+ except ValueError :
124
+ continue
131
125
return releases_with_dates
132
126
133
127
def get_sorted_releases (self ) -> list [str ]:
@@ -170,6 +164,25 @@ def __init__(
170
164
self .session : Session | None = Session () if pool_connections else None
171
165
self .request_hooks : dict [str , list [Any ]] = request_hooks or hooks .default_hooks ()
172
166
167
+ def _build_url (
168
+ self ,
169
+ package : str ,
170
+ version : str | None ,
171
+ ) -> str :
172
+ """
173
+ Construct a fully qualified PyPI API URL.
174
+
175
+ :param package: The package name
176
+ :param version: Optional version
177
+ :return: URL string
178
+ """
179
+
180
+ return (
181
+ f"{ self .base_url } /{ package } /{ version } /json"
182
+ if version
183
+ else f"{ self .base_url } /{ package } /json"
184
+ )
185
+
173
186
def request (
174
187
self ,
175
188
package : str | None = None ,
@@ -186,8 +199,11 @@ def request(
186
199
:param max_retries: Retry attempts for failed requests
187
200
:return: WoppResponse object with parsed data
188
201
:raises PackageNotProvidedError: if package is None
189
- :raises PackageNotFoundError: if the PyPI API returns 404
202
+ :raises PackageNotFoundError: if the PyPI API returns 404 or 5xx status codes
190
203
"""
204
+ if package is None :
205
+ raise PackageNotProvidedError
206
+
191
207
url = self ._build_url (package , version )
192
208
req_kwargs = {
193
209
"method" : "GET" ,
@@ -212,35 +228,15 @@ def request(
212
228
session .mount ("http://" , adapter )
213
229
session .mount ("https://" , adapter )
214
230
215
- response = session .send (
216
- prepared_request ,
217
- timeout = timeout ,
218
- allow_redirects = True ,
219
- )
220
-
221
- if response .status_code == 404 :
222
- raise PackageNotFoundError
223
-
224
- return WoppResponse (response .status_code , response .cleaned_json )
225
-
226
- def _build_url (
227
- self ,
228
- package : str | None ,
229
- version : str | None ,
230
- ) -> str :
231
- """
232
- Construct a fully qualified PyPI API URL.
233
-
234
- :param package: The package name
235
- :param version: Optional version
236
- :return: URL string
237
- :raises PackageNotProvidedError: if package is None
238
- """
239
- if package is None :
240
- raise PackageNotProvidedError
231
+ try :
232
+ response = session .send (
233
+ prepared_request ,
234
+ timeout = timeout ,
235
+ allow_redirects = True ,
236
+ )
237
+ if response .status_code == 404 or response .status_code >= 500 :
238
+ raise PackageNotFoundError # Treat all 5xx as failure to find package
239
+ except RequestException as e :
240
+ raise PackageNotFoundError from e
241
241
242
- return (
243
- f"{ self .base_url } /{ package } /{ version } /json"
244
- if version
245
- else f"{ self .base_url } /{ package } /json"
246
- )
242
+ return WoppResponse (response .status_code , getattr (response , "cleaned_json" , None ))
0 commit comments