From f6e620a3fdade741b96e64e01c1c74e32a8d6c0f Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Sun, 3 Feb 2019 11:14:51 -0500 Subject: [PATCH] Add Python End-of-Life detection --- TorrentToMedia.py | 3 + core/__init__.py | 26 +++++++ eol.py | 179 ++++++++++++++++++++++++++++++++++++++++++++++ nzbToMedia.py | 3 + 4 files changed, 211 insertions(+) create mode 100644 eol.py diff --git a/TorrentToMedia.py b/TorrentToMedia.py index 31fa152c..7704f9fc 100755 --- a/TorrentToMedia.py +++ b/TorrentToMedia.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # coding=utf-8 +import eol +eol.check() + import cleanup cleanup.clean(cleanup.FOLDER_STRUCTURE) diff --git a/core/__init__.py b/core/__init__.py index 136e4193..fbd471f7 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -13,6 +13,7 @@ import time import libs.autoload import libs.util +import eol if not libs.autoload.completed: sys.exit('Could not load vendored libraries.') @@ -1085,6 +1086,28 @@ def configure_utility_locations(): logger.warning('Install ffmpeg with x264 support to enable this feature ...') +def check_python(): + """Check End-of-Life status for Python version.""" + # Raise if end of life + eol.check() + + # Warn if within grace period + grace_period = 365 # days + eol.warn_for_status(grace_period=-grace_period) + + # Log warning if within grace period + days_left = eol.lifetime() + logger.info( + 'Python v{major}.{minor} will reach end of life in {x} days.'.format( + major=sys.version_info[0], + minor=sys.version_info[1], + x=days_left, + ) + ) + if days_left <= grace_period: + logger.warning('Please upgrade to a more recent Python version.') + + def initialize(section=None): global __INITIALIZED__ @@ -1101,6 +1124,9 @@ def initialize(section=None): configure_migration() configure_logging_part_2() + # check python version + check_python() + # initialize the main SB database main_db.upgrade_database(main_db.DBConnection(), databases.InitialSchema) diff --git a/eol.py b/eol.py new file mode 100644 index 00000000..a67bfc9e --- /dev/null +++ b/eol.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python + +import datetime +import sys +import warnings + +__version__ = '1.0.0' + + +def date(string, fmt='%Y-%m-%d'): + """ + Convert date string to date. + + :param string: A date string + :param fmt: Format to use when parsing the date string + :return: A datetime.date + """ + return datetime.datetime.strptime(string, fmt).date() + + +# https://devguide.python.org/ +# https://devguide.python.org/devcycle/#devcycle +PYTHON_EOL = { + (3, 7): date('2023-06-27'), + (3, 6): date('2021-12-23'), + (3, 5): date('2020-09-13'), + (3, 4): date('2019-03-16'), + (3, 3): date('2017-09-29'), + (3, 2): date('2016-02-20'), + (3, 1): date('2012-04-09'), + (3, 0): date('2009-01-13'), + (2, 7): date('2020-01-01'), + (2, 6): date('2013-10-29'), +} + + +class Error(Exception): + """An error has occurred.""" + + +class LifetimeError(Error): + """Lifetime has been exceeded and upgrade is required.""" + + +class LifetimeWarning(Warning): + """Lifetime has been exceeded and is no longer supported.""" + + +def lifetime(version=None): + """ + Calculate days left till End-of-Life for a version. + + :param version: An optional tuple with version information + If a version is not provided, the current system version will be used. + :return: Days left until End-of-Life + """ + if version is None: + version = sys.version_info + major = version[0] + minor = version[1] + now = datetime.datetime.now().date() + time_left = PYTHON_EOL[(major, minor)] - now + return time_left.days + + +def expiration(version=None, grace_period=0): + """ + Calculate expiration date for a version given a grace period. + + :param version: An optional tuple with version information + If a version is not provided, the current system version will be used. + :param grace_period: An optional number of days grace period + :return: Total days till expiration + """ + days_left = lifetime(version) + return days_left + grace_period + + +def check(version=None, grace_period=0): + """ + Raise an exception if end of life has been reached and recommend upgrade. + + :param version: An optional tuple with version information + If a version is not provided, the current system version will be used. + :param grace_period: An optional number of days grace period + If a grace period is not provided, a default 60 days grace period will + be used. + :return: None + """ + try: + raise_for_status(version, grace_period) + except LifetimeError as error: + print('Please use a newer version of Python.') + print_statuses() + sys.exit(error) + + +def raise_for_status(version=None, grace_period=0): + """ + Raise an exception if end of life has been reached. + + :param version: An optional tuple with version information + If a version is not provided, the current system version will be used. + :param grace_period: An optional number of days grace period + If a grace period is not provided, a default 60 days grace period will + be used. + :return: None + """ + if version is None: + version = sys.version_info + days_left = lifetime(version) + expires = days_left + grace_period + if expires <= 0: + msg = 'Python {major}.{minor} is no longer supported.'.format( + major=version[0], + minor=version[1], + ) + raise LifetimeError(msg) + + +def warn_for_status(version=None, grace_period=0): + """ + Warn if end of life has been reached. + + :param version: An optional tuple with version information + If a version is not provided, the current system version will be used. + :param grace_period: An optional number of days grace period + :return: None + """ + if version is None: + version = sys.version_info + days_left = lifetime(version) + expires = days_left + grace_period + if expires <= 0: + msg = 'Python {major}.{minor} is no longer supported.'.format( + major=version[0], + minor=version[1], + ) + warnings.warn(msg, LifetimeWarning) + + +def print_statuses(show_expired=False): + """ + Print end-of-life statuses of known python versions. + + :param show_expired: If true also print expired python version statuses + """ + lifetimes = sorted( + (lifetime(python_version), python_version) + for python_version in PYTHON_EOL + ) + print('Python End-of-Life for current versions:') + for days_left, python_version in lifetimes: + if days_left >= 0: + print( + 'v{major}.{minor} in {remaining:>4} days'.format( + major=python_version[0], + minor=python_version[1], + remaining=days_left, + ) + ) + if not show_expired: + return + + print() + print('Python End-of-Life for expired versions:') + for days_left, python_version in lifetimes: + if days_left < 0: + print( + 'v{major}.{minor} {remaining:>4} days ago'.format( + major=python_version[0], + minor=python_version[1], + remaining=-days_left, + ) + ) + + +if __name__ == '__main__': + print_statuses(show_expired=True) diff --git a/nzbToMedia.py b/nzbToMedia.py index f6c7e5db..a353e430 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -623,6 +623,9 @@ from __future__ import print_function +import eol +eol.check() + import cleanup cleanup.clean(cleanup.FOLDER_STRUCTURE)