@@ -153,71 +153,110 @@ def _is_repository_url(url):
153153 def _retrieve_pip_license (self , package , sources_url , dependency_description ):
154154 # Download the sources to get the license file inside
155155 self .output .info (f"Retrieving license for { package } " )
156- response = requests .get (sources_url )
157- response .raise_for_status ()
158-
159- with tempfile .TemporaryDirectory () as temp_dir :
160- sources_path = os .path .join (temp_dir , "sources.tar.gz" )
161- with open (sources_path , 'wb' ) as sources_file :
162- sources_file .write (response .content )
163-
164- with tarfile .open (sources_path , 'r:gz' ) as sources_archive :
165- license_file = "LICENSE"
166-
167- for source_file in sources_archive .getnames ():
168- if Path (source_file ).name == license_file :
169- sources_archive .extract (source_file , temp_dir )
170-
171- license_file_path = os .path .join (temp_dir , source_file )
172- with open (license_file_path , 'r' , encoding = 'utf8' ) as file :
173- dependency_description ["license_full" ] = file .read ()
156+ try :
157+ response = requests .get (sources_url )
158+ response .raise_for_status ()
159+
160+ with tempfile .TemporaryDirectory () as temp_dir :
161+ sources_path = os .path .join (temp_dir , "sources.tar.gz" )
162+ with open (sources_path , 'wb' ) as sources_file :
163+ sources_file .write (response .content )
164+
165+ with tarfile .open (sources_path , 'r:gz' ) as sources_archive :
166+ license_file = "LICENSE"
167+
168+ for source_file in sources_archive .getnames ():
169+ if Path (source_file ).name == license_file :
170+ sources_archive .extract (source_file , temp_dir )
171+
172+ license_file_path = os .path .join (temp_dir , source_file )
173+ with open (license_file_path , 'r' , encoding = 'utf8' ) as file :
174+ dependency_description ["license_full" ] = file .read ()
175+ break
176+ except Exception as e :
177+ self .output .warning (f"Failed to retrieve license for { package } from { sources_url } : { e } " )
178+ # Don't fail the build, just continue without the license
174179
175180 def _make_pip_dependency_description (self , package , version , dependencies ):
176181 url = ["https://pypi.org/pypi" , package ]
177182 if version is not None :
178- url .append (version )
183+ # Strip local version identifiers (everything after '+') for PyPI API compatibility
184+ # e.g., "1.26.1+mkl" becomes "1.26.1"
185+ clean_version = version .split ('+' )[0 ] if '+' in version else version
186+ url .append (clean_version )
179187 url .append ("json" )
180188
181- data = requests .get ("/" .join (url )).json ()
189+ try :
190+ response = requests .get ("/" .join (url ))
191+ response .raise_for_status ()
192+ data = response .json ()
193+ except (requests .RequestException , ValueError ) as e :
194+ self .output .warning (f"Failed to retrieve PyPI data for { package } : { e } " )
195+ # Create minimal dependency description with fallback values
196+ dependencies [package ] = {
197+ "summary" : f"Package { package } " ,
198+ "version" : version or "unknown" ,
199+ "license" : "unknown"
200+ }
201+ return
202+
203+ # Check if the response has the expected structure
204+ if "info" not in data :
205+ self .output .warning (f"PyPI response for { package } missing 'info' field" )
206+ dependencies [package ] = {
207+ "summary" : f"Package { package } " ,
208+ "version" : version or "unknown" ,
209+ "license" : "unknown"
210+ }
211+ return
182212
213+ info = data ["info" ]
183214 dependency_description = {
184- "summary" : data [ " info" ][ " summary"] ,
185- "version" : data [ " info" ][ " version"],
186- "license" : data [ " info" ][ " license"]
215+ "summary" : info . get ( " summary", f"Package { package } " ) ,
216+ "version" : version or info . get ( " version", "unknown" ), # Use original version if available
217+ "license" : info . get ( " license", "unknown" )
187218 }
188219
189- for url_data in data ["urls" ]:
190- if url_data ["packagetype" ] == "sdist" :
191- sources_url = url_data ["url" ]
192- dependency_description ["sources_url" ] = sources_url
193-
194- if not self .options .skip_licenses_download :
195- self ._retrieve_pip_license (package , sources_url , dependency_description )
196-
197- for source_url , check_source in [("source" , False ),
198- ("Source" , False ),
199- ("Source Code" , False ),
200- ("Repository" , False ),
201- ("Code" , False ),
202- ("homepage" , True ),
203- ("Homepage" , True )]:
204- try :
205- url = data ["info" ]["project_urls" ][source_url ]
206- if check_source and not self ._is_repository_url (url ):
207- # That will not work for ALL open-source projects, but should already get a large majority of them
208- self .output .warning (f"Source URL for { package } ({ url } ) doesn't seem to be a supported repository" )
209- continue
210- dependency_description ["sources_url" ] = url
211- break
212- except KeyError :
213- pass
220+ # Handle URLs section safely
221+ if "urls" in data :
222+ for url_data in data ["urls" ]:
223+ if url_data .get ("packagetype" ) == "sdist" :
224+ sources_url = url_data .get ("url" )
225+ if sources_url :
226+ dependency_description ["sources_url" ] = sources_url
227+
228+ if not self .options .skip_licenses_download :
229+ try :
230+ self ._retrieve_pip_license (package , sources_url , dependency_description )
231+ except Exception as e :
232+ self .output .warning (f"Failed to retrieve license for { package } : { e } " )
233+
234+ # Handle project URLs safely
235+ if "project_urls" in info :
236+ for source_url , check_source in [("source" , False ),
237+ ("Source" , False ),
238+ ("Source Code" , False ),
239+ ("Repository" , False ),
240+ ("Code" , False ),
241+ ("homepage" , True ),
242+ ("Homepage" , True )]:
243+ try :
244+ url = info ["project_urls" ][source_url ]
245+ if check_source and not self ._is_repository_url (url ):
246+ # That will not work for ALL open-source projects, but should already get a large majority of them
247+ self .output .warning (f"Source URL for { package } ({ url } ) doesn't seem to be a supported repository" )
248+ continue
249+ dependency_description ["sources_url" ] = url
250+ break
251+ except (KeyError , TypeError ):
252+ pass
214253
215254 if dependency_description ["license" ] is not None and len (dependency_description ["license" ]) > 32 :
216255 # Some packages have their full license in this field
217256 dependency_description ["license_full" ] = dependency_description ["license" ]
218- dependency_description ["license" ] = data [ " info" ][ " name"]
257+ dependency_description ["license" ] = info . get ( " name", package )
219258
220- dependencies [data [ " info" ][ " name"] ] = dependency_description
259+ dependencies [info . get ( " name", package ) ] = dependency_description
221260
222261 @staticmethod
223262 def _get_license_from_repository (sources_url , version , license_file_name = None ):
0 commit comments