11# Standard Libraries
22import json
33import re
4+ from datetime import datetime
45
56import pandas as pd
67
1314JSON_FILE = "resources/table.json"
1415REPORT_FILE = "docs/report.md"
1516PIP_INDEX_URL = "https://pypi.org/pypi"
17+ PEP_BASE_URL = "https://peps.python.org/"
1618
1719SPEC_MAP = {
1820 "ASGI" : "https://asgi.readthedocs.io/en/latest/specs/main.html" ,
1921 "WSGI" : "https://peps.python.org/numerical" ,
2022}
2123
2224
23- def get_upstream_version (dependency ):
25+ def estimate_days_behind (release_date ):
26+ return datetime .today () - datetime .strptime (release_date , "%Y-%m-%d" )
27+
28+
29+ def get_upstream_version (dependency , last_supported_version ):
2430 """Get the latest version available upstream"""
31+ last_supported_version_release_date = "Not found"
2532 if dependency in SPEC_MAP :
2633 # webscrape info from official website
27- pattern = "(\d+\.\d+\.?\d*)"
34+ version_pattern = "(\d+\.\d+\.?\d*)"
35+ latest_version_release_date = ""
2836
2937 url = SPEC_MAP [dependency ]
3038 page = requests .get (url )
3139 soup = BeautifulSoup (page .text , "html.parser" )
3240 # ASGI
3341 if "asgi" in url :
34- text = (
35- soup .find (id = "version-history" )
36- .findChild ("li" , string = re .compile (pattern ))
37- .text
38- )
42+ all_versions = soup .find (id = "version-history" ).find_all ("li" )
43+ pattern = re .compile (r"([\d.]+) \((\d{4}-\d{2}-\d{2})\)" )
44+ latest_version , latest_version_release_date = pattern .search (
45+ all_versions [0 ].text
46+ ).groups ()
47+ for li in all_versions :
48+ match = pattern .search (li .text )
49+ if match :
50+ version , date = match .groups ()
51+ if version == last_supported_version :
52+ last_supported_version_release_date = date
53+ break
3954 # WSGI
4055 else :
41- tag = soup .find (id = "numerical-index" ).find_all (
56+ all_versions = soup .find (id = "numerical-index" ).find_all (
4257 "a" , string = re .compile ("Web Server Gateway Interface" )
43- )[- 1 ]
44- text = tag .text
45- res = re .search (pattern , text )
46- return res [1 ]
58+ )
59+ latest_version = re .search (version_pattern , all_versions [- 1 ].text ).group ()
60+
61+ for a in all_versions :
62+ pep_link = PEP_BASE_URL + a .get ("href" ).split (".." )[1 ]
63+ response = requests .get (pep_link )
64+ soup = BeautifulSoup (response .text , "html.parser" )
65+ version = re .search (version_pattern , a .text ).group ()
66+ pep_page_metadata = soup .find ("dl" )
67+
68+ if pep_page_metadata and version in [
69+ latest_version ,
70+ last_supported_version ,
71+ ]:
72+ metadata_fields = pep_page_metadata .find_all ("dt" )
73+ metadata_values = pep_page_metadata .find_all ("dd" )
74+
75+ for dt , dd in zip (metadata_fields , metadata_values ):
76+ if "Created" in dt .text :
77+ release_date = dd .text .strip ()
78+ release_date_as_datetime = datetime .strptime (
79+ release_date , "%d-%b-%Y"
80+ )
81+ if version == latest_version :
82+ latest_version_release_date = (
83+ release_date_as_datetime .strftime ("%Y-%m-%d" )
84+ )
85+ if version == last_supported_version :
86+ last_supported_version_release_date = (
87+ release_date_as_datetime .strftime ("%Y-%m-%d" )
88+ )
89+ return (
90+ latest_version ,
91+ latest_version_release_date ,
92+ last_supported_version_release_date ,
93+ )
4794
4895 else :
4996 # get info using PYPI API
5097 response = requests .get (f"{ PIP_INDEX_URL } /{ dependency } /json" )
5198 response_json = response .json ()
5299 latest_version = response_json ["info" ]["version" ]
53- return latest_version
100+ release_time = response_json ["releases" ][latest_version ][- 1 ][
101+ "upload_time_iso_8601"
102+ ]
103+ latest_version_release_date = datetime .fromisoformat (release_time )
104+ formatted_release_date = latest_version_release_date .strftime ("%Y-%m-%d" )
105+ for version , release_info in response_json ["releases" ].items ():
106+ if version == last_supported_version :
107+ release_time = release_info [- 1 ]["upload_time_iso_8601" ]
108+ release_date = datetime .fromisoformat (release_time )
109+ last_supported_version_release_date = release_date .strftime ("%Y-%m-%d" )
110+ return (
111+ latest_version ,
112+ formatted_release_date ,
113+ last_supported_version_release_date ,
114+ )
54115
55116
56117def get_last_supported_version (tekton_ci_output , dependency ):
@@ -67,14 +128,18 @@ def get_last_supported_version(tekton_ci_output, dependency):
67128 return last_supported_version [1 ]
68129
69130
70- def isUptodate (last_supported_version , latest_version ):
131+ def is_up_to_date (
132+ last_supported_version , latest_version , last_supported_version_release_date
133+ ):
71134 """Check if the supported package is up-to-date"""
72135 if Version (last_supported_version ) >= Version (latest_version ):
73136 up_to_date = "Yes"
137+ days_behind = 0
74138 else :
75139 up_to_date = "No"
140+ days_behind = estimate_days_behind (last_supported_version_release_date )
76141
77- return up_to_date
142+ return up_to_date , days_behind
78143
79144
80145def get_taskruns (namespace , task_name , taskrun_filter ):
@@ -144,15 +209,15 @@ def get_tekton_ci_output():
144209 core_v1_client = client .CoreV1Api ()
145210
146211 task_name = "python-tracer-unittest-gevent-starlette-task"
147- taskrun_filter = lambda tr : tr ["status" ]["conditions" ][0 ]["type" ] == "Succeeded"
212+ taskrun_filter = lambda tr : tr ["status" ]["conditions" ][0 ]["type" ] == "Succeeded" # noqa: E731
148213 starlette_taskruns = get_taskruns (namespace , task_name , taskrun_filter )
149214
150215 tekton_ci_output = process_taskrun_logs (
151216 starlette_taskruns , core_v1_client , namespace , task_name , ""
152217 )
153218
154219 task_name = "python-tracer-unittest-googlecloud-task"
155- taskrun_filter = (
220+ taskrun_filter = ( # noqa: E731
156221 lambda tr : tr ["metadata" ]["name" ].endswith ("unittest-googlecloud-0" )
157222 and tr ["status" ]["conditions" ][0 ]["type" ] == "Succeeded"
158223 )
@@ -163,7 +228,7 @@ def get_tekton_ci_output():
163228 )
164229
165230 task_name = "python-tracer-unittest-default-task"
166- taskrun_filter = (
231+ taskrun_filter = ( # noqa: E731
167232 lambda tr : tr ["metadata" ]["name" ].endswith ("unittest-default-3" )
168233 and tr ["status" ]["conditions" ][0 ]["type" ] == "Succeeded"
169234 )
@@ -195,11 +260,23 @@ def main():
195260 else :
196261 last_supported_version = item ["Last Supported Version" ]
197262
198- latest_version = get_upstream_version (package )
263+ latest_version , release_date , last_supported_version_release_date = (
264+ get_upstream_version (package , last_supported_version )
265+ )
199266
200- up_to_date = isUptodate (last_supported_version , latest_version )
267+ up_to_date , days_behind = is_up_to_date (
268+ last_supported_version , latest_version , last_supported_version_release_date
269+ )
201270
202- item .update ({"Latest version" : latest_version , "Up-to-date" : up_to_date })
271+ item .update (
272+ {
273+ "Latest version" : latest_version ,
274+ "Up-to-date" : up_to_date ,
275+ "Release date" : release_date ,
276+ "Latest Version Published At" : last_supported_version_release_date ,
277+ "Days behind" : f"{ days_behind } day/s" ,
278+ }
279+ )
203280
204281 # Create a DataFrame from the list of dictionaries
205282 df = pd .DataFrame (items )
0 commit comments