Source code for python_active_versions.python_active_versions

# SPDX-FileCopyrightText: 2023 - 2024 Gabriele Pongelli
#
# SPDX-License-Identifier: MIT

"""Main module."""
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List

from requests_cache import CacheMixin
from requests_html import HTMLResponse, HTMLSession

from python_active_versions.utility import configure_logger


[docs] class CachedHTMLSession(CacheMixin, HTMLSession): # pylint: disable=W0223 """Session with features from both CachedSession and HTMLSession."""
def _fetch_tags(package: str, version: str) -> List: """Fetch available docker tags. Arguments: package: package name to be fetched, default to python version: python's version to fetch docker images Returns: list of docker imaged of package at that version """ _names = [] _next_page = True _page = 1 session = CachedHTMLSession(backend='sqlite', cache_control=True, expire_after=604800) # one week while _next_page: logging.info("Fetching docker tags for %s %s , page %s", package, version, _page) result = session.get( f"https://registry.hub.docker.com/v2/repositories/library/{package}/tags?name={version}&page={_page}", timeout=120, ) _json = result.json() if not _json['next']: _next_page = False _page += 1 _names.extend([r["name"] for r in _json['results']]) return _names
[docs] def get_active_python_versions( docker_images: bool = False, log_level: str = 'INFO', no_main: bool = True ) -> List[dict]: # pylint: disable=too-many-locals """Get active python versions. Arguments: docker_images: flag to return also available docker images log_level: string indicating log level on stdout no_main: Filter out "main" branch that has no explicit version numbering. Returns: dict containing all information of active python versions. """ configure_logger(log_level) versions = [] version_table_selector = "#status-of-python-versions table" session = CachedHTMLSession(backend='sqlite', cache_control=True, expire_after=604800) # one week _versions: HTMLResponse = session.get("https://devguide.python.org/versions/") version_table = _versions.html.find(version_table_selector, first=True) # match development information with the latest downloadable release _py_specific_release = ".download-list-widget li" _downloads: HTMLResponse = session.get("https://www.python.org/downloads/") spec_table = _downloads.html.find(_py_specific_release) _downloadable_versions = [li.find('span a', first=True).text.split(' ')[1] for li in spec_table] def worker(ver, no_main_branch): branch, _, _, first_release, end_of_life, _ = [v.text for v in ver.find("td")] if no_main_branch is True and branch == 'main': return logging.info("Found Python branch: %s", branch) _matching_version = list( filter(lambda d: d.startswith(branch), _downloadable_versions) # pylint: disable=cell-var-from-loop ) _latest_sw = branch if _matching_version: _latest_sw = _matching_version[0] _d = {"version": branch, "latest_sw": _latest_sw, "start": first_release, "end": end_of_life} if docker_images: _d['docker_images'] = _fetch_tags('python', _latest_sw) versions.append(_d) with ThreadPoolExecutor(max_workers=4) as executor: futures = [executor.submit(worker, tr, no_main) for tr in version_table.find("tbody tr")] as_completed(futures, 10) return versions