2222
2323import asyncio
2424from collections import namedtuple
25+ import aiohttp
26+ from bs4 import BeautifulSoup
2527from dateutil import parser
2628from json import JSONDecodeError
2729from typing import Mapping
3234from aiohttp import ClientSession
3335from aiohttp .client_exceptions import ClientResponseError
3436from aiohttp .client_exceptions import ServerDisconnectedError
37+ from requests .sessions import session
3538
3639
3740Version = namedtuple ("Version" , field_names = ["value" , "release_date" ])
@@ -393,22 +396,51 @@ class GitHubTagsAPI(VersionAPI):
393396 package_type = "github"
394397
395398 async def load_api (self , repo_set ):
396- async with client_session () as session :
399+ session = client_session ()
400+ async with session as session :
397401 await asyncio .gather (
398402 * [
399- self .fetch (owner_repo .lower (), session )
403+ self .fetch (owner_repo .lower ())
400404 for owner_repo in repo_set
401405 if owner_repo .lower () not in self .cache
402406 ]
403407 )
404408
405- async def fetch (self , owner_repo : str , session ) -> None :
409+ async def fetch (self , owner_repo : str , endpoint = None ) -> None :
406410 # owner_repo is a string of format "{repo_owner}/{repo_name}"
407411 # Example value of owner_repo = "nexB/scancode-toolkit"
408- endpoint = f"https://api.github.com/repos/{ owner_repo } /git/refs/tags"
409- resp = await session .request (method = "GET" , url = endpoint )
410- resp = await resp .json ()
411- self .cache [owner_repo ] = [release ["ref" ].split ("/" )[- 1 ] for release in resp ]
412+ if owner_repo not in self .cache :
413+ self .cache [owner_repo ] = set ()
414+
415+ if not endpoint :
416+ endpoint = f"https://github.com/{ owner_repo } /tags"
417+ async with client_session () as session :
418+ resp = await session .get (endpoint )
419+ resp = await resp .read ()
420+
421+ soup = BeautifulSoup (resp , features = "lxml" )
422+ for release_entry in soup .find_all ("div" , {"class" : "commit" }):
423+ version = None
424+ for links in release_entry .find_all ("a" ):
425+ if f"/{ owner_repo } /releases/tag/" in links ["href" ].lower ():
426+ version = links ["href" ].split ("/" )[- 1 ]
427+ break
428+
429+ release_date = release_entry .find ("relative-time" )["datetime" ]
430+ self .cache [owner_repo ].add (
431+ Version (value = version , release_date = parser .parse (release_date ))
432+ )
433+
434+ url = None
435+ pagination_links = soup .find ("div" , {"class" : "paginate-container" }).find_all ("a" )
436+ for link in pagination_links :
437+ if link .text == "Next" :
438+ url = link ["href" ]
439+ break
440+
441+ if url :
442+ # FIXME: this could be asynced to improve performance
443+ await self .fetch (owner_repo , url )
412444
413445
414446class HexVersionAPI (VersionAPI ):
@@ -425,7 +457,11 @@ async def fetch(self, pkg, session):
425457 response = await session .request (method = "GET" , url = url )
426458 response = await response .json ()
427459 for release in response ["releases" ]:
428- versions .add (release ["version" ])
460+ versions .add (
461+ Version (
462+ value = release ["version" ], release_date = parser .parse (release ["inserted_at" ])
463+ )
464+ )
429465 except (ClientResponseError , JSONDecodeError ):
430466 pass
431467
0 commit comments