/ dev / show_package_release_dates.py
show_package_release_dates.py
 1  import asyncio
 2  import json
 3  import subprocess
 4  import sys
 5  import traceback
 6  from collections.abc import Sequence
 7  
 8  import aiohttp
 9  from pydantic import BaseModel
10  
11  
12  class ReleaseFile(BaseModel):
13      upload_time: str
14  
15  
16  class PyPIResponse(BaseModel):
17      releases: dict[str, list[ReleaseFile]]
18  
19  
20  def get_distributions() -> list[tuple[str, str]]:
21      res = subprocess.check_output([sys.executable, "-m", "pip", "list", "--format", "json"])
22      return [(pkg["name"], pkg["version"]) for pkg in json.loads(res)]
23  
24  
25  def extract_upload_time(response: PyPIResponse, version: str) -> str | None:
26      for f in response.releases.get(version, []):
27          return f.upload_time.replace("T", " ")
28      return None
29  
30  
31  async def get_release_date(
32      session: aiohttp.ClientSession, package: str, version: str
33  ) -> str | None:
34      try:
35          async with session.get(f"https://pypi.python.org/pypi/{package}/json", timeout=10) as resp:
36              resp.raise_for_status()
37              resp_json = await resp.json()
38              response = PyPIResponse.model_validate(resp_json)
39              return extract_upload_time(response, version)
40  
41      except Exception:
42          traceback.print_exc()
43          return None
44  
45  
46  def get_longest_string_length(array: Sequence[str]) -> int:
47      return len(max(array, key=len))
48  
49  
50  async def main() -> None:
51      distributions = get_distributions()
52      async with aiohttp.ClientSession() as session:
53          tasks = [get_release_date(session, pkg, ver) for pkg, ver in distributions]
54          results = await asyncio.gather(*tasks)
55  
56      release_dates = [r or "" for r in results]
57      packages, versions = list(zip(*distributions))
58      package_length = get_longest_string_length(packages)
59      version_length = get_longest_string_length(versions)
60      release_timestamp_length = get_longest_string_length(release_dates)
61      print(
62          "Package".ljust(package_length),
63          "Version".ljust(version_length),
64          "Release Timestamp".ljust(release_timestamp_length),
65      )
66      print("-" * (package_length + version_length + release_timestamp_length + 2))
67      for package, version, release_date in sorted(
68          zip(packages, versions, release_dates),
69          # Sort by release date in descending order
70          key=lambda x: x[2],
71          reverse=True,
72      ):
73          print(
74              package.ljust(package_length),
75              version.ljust(version_length),
76              release_date.ljust(release_timestamp_length),
77          )
78  
79  
80  if __name__ == "__main__":
81      asyncio.run(main())