diff --git a/barcalendar.py b/barcalendar.py index 4e07e63b..64abcc4f 100644 --- a/barcalendar.py +++ b/barcalendar.py @@ -22,9 +22,87 @@ import re import time +import atexit +import logging import requests import yaml +def setup_custom_logging(): + """ + Creating a custom logger to flush all Warning logs at the end of the script + to avoid breaking the console script being generated by the script + """ + class DelayedLogHandler(logging.Handler): + def __init__(self): + super().__init__() + self.log_messages = [] + + def emit(self, record): + self.log_messages.append(record) + + def _flush_log_messages(handler): + for message in handler.log_messages: + print(message) + + # Create a custom log handler + custom_log_handler = DelayedLogHandler() + custom_log_handler.setLevel(logging.WARNING) + + # Create custom logger and attach custom_handler to it + eol_logger = logging.getLogger("eol_logger") + eol_logger.addHandler(custom_log_handler) + + # Register a function to flush log messages at exit + atexit.register(_flush_log_messages, custom_log_handler) + + # Return custom logger to use in the script + return eol_logger + +def validate_version_date(project, version, year, month, check_start=False): + """ + Validates the end-of-life(EOL) or release date for a project version. + Returns the correct EOL/releaseDate in case of a conflict with api value. + + Parameters: + project (str): The name of the project. + version (str): The version of the project. + year (int): The year of the EOL/release date. + month (int): The month of the EOL/release date. + check_start (bool): Boolean flag to check either EOL or release date + + Returns: + tuple: A tuple representing the validated EOL/release date (year, month) + """ + try: + response = requests.get( + url=f"https://endoflife.date/api/{project}/{version}.json", + headers={'Accept': 'application/json'} + ) + + if response.status_code != 200: + eol_logger.error(f"Project not Found: {project}/{version} not found on endoflife.date") + return (year, month) + + version_info = response.json() + if check_start: + eol_year, eol_month, _ = version_info['releaseDate'].split("-") + date_type = "releaseDate" + else: + if version_info['eol'] == False: + eol_logger.warning(f"No EOL found for {project} {version}") + return (year, month) + eol_year, eol_month, _ = version_info['eol'].split("-") + date_type = "eol" + + if (year, month) == (int(eol_year), int(eol_month)): + return (year, month) + else: + eol_logger.warning(f"Invalid EOL: Update {project} {version} {date_type} to {eol_year}/{eol_month} instead") + return (int(eol_year), int(eol_month)) + except requests.exceptions.RequestException as e: + eol_logger.error(f"API request failed with an exception: {str(e)}") + return (year, month) + def css_to_rgb(hex): assert hex[0] == "#" return [int(h, 16)/255 for h in [hex[1:3], hex[3:5], hex[5:7]]] @@ -312,6 +390,7 @@ def parse_version_name(line): return version_name.capitalize() raise ValueError(f"Couldn't get version name from {line!r}") +eol_logger = setup_custom_logging() # ==== Editable content ==== # Global Options @@ -420,6 +499,7 @@ def parse_version_name(line): ('4.2', 2023, 4, True, "Django 4.2 work is being tracked in https://github.com/openedx/platform-roadmap/issues/269"), ] for name, year, month, lts, *more in django_releases: + year, month = validate_version_date("Django", name, year, month, check_start=True) if LTS_ONLY and not lts: continue length = 3*12 if lts else 16 @@ -449,6 +529,7 @@ def parse_version_name(line): ('3.12', 2023, 10, 2028, 10), # https://peps.python.org/pep-0693/ ] for name, syear, smonth, eyear, emonth in python_releases: + eyear, emonth = validate_version_date("Python", name, eyear, emonth) cal.bar( f"Python {name}", start=(syear, smonth), @@ -477,6 +558,7 @@ def parse_version_name(line): nick = ubuntu_nicks.get(name, '') if nick: nick = f" {nick}" + year, month = validate_version_date("Ubuntu", name, 2000+year, month, check_start=True) cal.bar( f"Ubuntu {name}{nick}", (2000+year, month), @@ -500,6 +582,7 @@ def parse_version_name(line): ('18.x', 2022, 4, 2025, 4), ] for name, syear, smonth, eyear, emonth in node_releases: + eyear, emonth = validate_version_date("NodeJS", name, eyear, emonth) cal.bar( f"Node {name}", start=(syear, smonth), @@ -523,6 +606,7 @@ def parse_version_name(line): ('4.4', 2020, 7, 2024, 2), ] for name, syear, smonth, eyear, emonth in mongo_releases: + eyear, emonth = validate_version_date("mongo", name, eyear, emonth) cal.bar( f"Mongo {name}", start=(syear, smonth), @@ -542,6 +626,7 @@ def parse_version_name(line): ('8.1', 2023, 6, 2024, 9), # Not sure of the real support dates. ] for name, syear, smonth, eyear, emonth in mysql_releases: + eyear, emonth = validate_version_date("MySQL", name, eyear, emonth) cal.bar( f"MySQL {name}", start=(syear, smonth), @@ -568,6 +653,7 @@ def parse_version_name(line): ('7.17', 2022, 2, 2023, 8), ] for name, syear, smonth, eyear, emonth in es_releases: + eyear, emonth = validate_version_date("ElasticSearch", name, eyear, emonth) cal.bar( f"Elasticsearch {name}", start=(syear, smonth), @@ -588,6 +674,7 @@ def parse_version_name(line): ('7.0', 2022, 4, 2025, 4), ] for name, syear, smonth, eyear, emonth in redis_releases: + eyear, emonth = validate_version_date("Redis", name, eyear, emonth) cal.bar( f"Redis {name}", start=(syear, smonth), @@ -611,6 +698,7 @@ def parse_version_name(line): ('3.1', 2021, 12, 2025, 3), ] for name, syear, smonth, eyear, emonth, *more in ruby_releases: + eyear, emonth = validate_version_date("Ruby", name, eyear, emonth) cal.bar( f"Ruby {name}", start=(syear, smonth),