From e4eb77ec1d55df9117a3f6eaf3e307a5fe9f7582 Mon Sep 17 00:00:00 2001 From: "Jason M. Gates" Date: Mon, 2 Dec 2024 11:52:52 -0700 Subject: [PATCH 1/3] chore!: Drop support for Python 3.8 * Use type-hinting out of the box in 3.9. * Use new dictionary update syntax. * Update the CI and docs accordingly. --- .github/workflows/continuous-integration.yml | 2 +- README.md | 2 +- doc/source/index.rst | 2 +- pyproject.toml | 1 - shell_logger/html_utilities.py | 18 +++++++++--------- shell_logger/shell.py | 6 +++--- shell_logger/shell_logger.py | 10 +++++----- shell_logger/stats_collector.py | 8 ++++---- test/test_shell_logger.py | 8 +++++--- 9 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 91eaf81..857423d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -19,7 +19,7 @@ jobs: runs-on: macos-latest strategy: matrix: - version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + version: ["3.9", "3.10", "3.11", "3.12"] steps: - name: Harden Runner diff --git a/README.md b/README.md index ed00e33..4534f13 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ [![pre-commit.ci Status](https://results.pre-commit.ci/badge/github/sandialabs/shell-logger/master.svg)](https://results.pre-commit.ci/latest/github/sandialabs/shell-logger/master) [![PyPI - Version](https://img.shields.io/pypi/v/shell-logger-sandialabs?label=PyPI)](https://pypi.org/project/shell-logger-sandialabs/) ![PyPI - Downloads](https://img.shields.io/pypi/dm/shell-logger-sandialabs?label=PyPI%20downloads) -![Python Version](https://img.shields.io/badge/Python-3.8|3.9|3.10|3.11|3.12-blue.svg) +![Python Version](https://img.shields.io/badge/Python-3.9|3.10|3.11|3.12-blue.svg) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) > **NOTICE:** After using this package for a few years, we realized we'd diff --git a/doc/source/index.rst b/doc/source/index.rst index 24d836f..3229f0f 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -64,7 +64,7 @@ shell-logger .. |PyPI Version| image:: https://img.shields.io/pypi/v/shell-logger-sandialabs?label=PyPI :target: https://pypi.org/project/shell-logger-sandialabs/ .. |PyPI Downloads| image:: https://img.shields.io/pypi/dm/shell-logger-sandialabs?label=PyPI%20downloads -.. |Python Version| image:: https://img.shields.io/badge/Python-3.8|3.9|3.10|3.11|3.12-blue.svg +.. |Python Version| image:: https://img.shields.io/badge/Python-3.9|3.10|3.11|3.12-blue.svg .. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json :target: https://github.com/astral-sh/ruff diff --git a/pyproject.toml b/pyproject.toml index 1c9756e..b5c11be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ classifiers = [ "Operating System :: POSIX", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/shell_logger/html_utilities.py b/shell_logger/html_utilities.py index 517d58f..1819673 100644 --- a/shell_logger/html_utilities.py +++ b/shell_logger/html_utilities.py @@ -13,7 +13,7 @@ from datetime import datetime from pathlib import Path from types import SimpleNamespace -from typing import Iterator, List, TextIO, Tuple, Union +from typing import Iterator, TextIO, Union def nested_simplenamespace_to_dict( @@ -157,7 +157,7 @@ def flatten(element: Union[str, bytes, Iterable]) -> Iterator[str]: def parent_logger_card_html( - name: str, *args: List[Iterator[str]] + name: str, *args: list[Iterator[str]] ) -> Iterator[str]: """ Generate the HTML for a parent logger card. @@ -212,7 +212,7 @@ def child_logger_card(log) -> Iterator[str]: def child_logger_card_html( - name: str, duration: str, *args: Union[Iterator[str], List[Iterator[str]]] + name: str, duration: str, *args: Union[Iterator[str], list[Iterator[str]]] ) -> Iterator[str]: """ Generate the HTML for a child logger card. @@ -480,7 +480,7 @@ def command_card(log: dict, stream_dir: Path) -> Iterator[str]: def time_series_plot( - cmd_id: str, data_tuples: List[Tuple[float, float]], series_title: str + cmd_id: str, data_tuples: list[tuple[float, float]], series_title: str ) -> Iterator[str]: """ Create the HTML for a plot of time series data. @@ -501,7 +501,7 @@ def time_series_plot( def disk_time_series_plot( - cmd_id: str, data_tuples: Tuple[float, float], volume_name: str + cmd_id: str, data_tuples: tuple[float, float], volume_name: str ) -> Iterator[str]: """ Generate a time series plot of disk usage. @@ -530,7 +530,7 @@ def disk_time_series_plot( def stat_chart_card( - labels: List[str], data: List[float], title: str, identifier: str + labels: list[str], data: list[float], title: str, identifier: str ) -> Iterator[str]: """ Create the HTML for a two-dimensional plot. @@ -676,7 +676,7 @@ def output_block_html( def split_template( template: str, split_at: str, **kwargs -) -> Tuple[str, str, str]: +) -> tuple[str, str, str]: """ Subdivide a HTML template. @@ -854,7 +854,7 @@ def sgr_4bit_color_and_style_to_html(sgr: str) -> str: return f'' -def sgr_8bit_color_to_html(sgr_params: List[str]) -> str: # noqa: PLR0911 +def sgr_8bit_color_to_html(sgr_params: list[str]) -> str: # noqa: PLR0911 """ Convert 8-bit SGR colors to HTML. @@ -894,7 +894,7 @@ def sgr_8bit_color_to_html(sgr_params: List[str]) -> str: # noqa: PLR0911 return "THIS SHOULD NEVER HAPPEN" -def sgr_24bit_color_to_html(sgr_params: List[str]) -> str: +def sgr_24bit_color_to_html(sgr_params: list[str]) -> str: """ Convert 24-bit SGR colors to HTML. diff --git a/shell_logger/shell.py b/shell_logger/shell.py index a5cb040..541369d 100644 --- a/shell_logger/shell.py +++ b/shell_logger/shell.py @@ -18,7 +18,7 @@ from threading import Thread from time import time from types import SimpleNamespace -from typing import IO, List, Optional, TextIO, Tuple +from typing import IO, Optional, TextIO END_OF_READ = 4 @@ -262,7 +262,7 @@ def tee( # noqa: C901 stdout_tee = [sys_stdout, stdout_io, stdout_path] stderr_tee = [sys_stderr, stderr_io, stderr_path] - def write(input_file: TextIO, output_files: List[TextIO]) -> None: + def write(input_file: TextIO, output_files: list[TextIO]) -> None: """ Write an input to multiple outputs. @@ -320,7 +320,7 @@ def write(input_file: TextIO, output_files: List[TextIO]) -> None: def auxiliary_command( self, **kwargs - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[Optional[str], Optional[str]]: """ Run auxiliary commands like `umask`, `pwd`, `env`, etc. diff --git a/shell_logger/shell_logger.py b/shell_logger/shell_logger.py index d9f8f9e..a4b6b02 100644 --- a/shell_logger/shell_logger.py +++ b/shell_logger/shell_logger.py @@ -19,7 +19,7 @@ from distutils import dir_util from pathlib import Path from types import SimpleNamespace -from typing import Iterator, List, Optional, Union +from typing import Iterator, Optional, Union from .html_utilities import ( append_html, @@ -139,7 +139,7 @@ def __init__( # noqa: PLR0913 html_file: Optional[Path] = None, indent: int = 0, login_shell: bool = False, - log: Optional[List[object]] = None, + log: Optional[list[object]] = None, init_time: Optional[datetime] = None, done_time: Optional[datetime] = None, duration: Optional[str] = None, @@ -179,7 +179,7 @@ def __init__( # noqa: PLR0913 generally be omitted. """ self.name = name - self.log_book: List[Union[dict, ShellLogger]] = ( + self.log_book: list[Union[dict, ShellLogger]] = ( log if log is not None else [] ) self.init_time = datetime.now() if init_time is None else init_time @@ -404,7 +404,7 @@ def html_print(self, msg: str, msg_title: str = "HTML Message") -> None: } self.log_book.append(log) - def to_html(self) -> Union[Iterator[str], List[Iterator[str]]]: + def to_html(self) -> Union[Iterator[str], list[Iterator[str]]]: """ Convert the log entries to HTML. @@ -591,7 +591,7 @@ def log( # noqa: PLR0913 s = int(result.wall / 1000) % 60 log["duration"] = f"{h}h {m}m {s}s" log["return_code"] = result.returncode - log = {**log, **nested_simplenamespace_to_dict(result)} + log |= nested_simplenamespace_to_dict(result) self.log_book.append(log) return { "return_code": log["return_code"], diff --git a/shell_logger/stats_collector.py b/shell_logger/stats_collector.py index 0803104..cd99525 100644 --- a/shell_logger/stats_collector.py +++ b/shell_logger/stats_collector.py @@ -13,7 +13,7 @@ from multiprocessing import Manager, Process from pathlib import Path from time import sleep, time -from typing import List, Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING from .abstract_method import AbstractMethod @@ -26,7 +26,7 @@ psutil = None -def stats_collectors(**kwargs) -> List[StatsCollector]: +def stats_collectors(**kwargs) -> list[StatsCollector]: """ Generate stats collectors. @@ -236,7 +236,7 @@ def collect(self) -> None: timestamp = round(time() * milliseconds_per_second) self.stats.append((timestamp, psutil.cpu_percent(interval=None))) - def unproxied_stats(self) -> List[Tuple[float, float]]: + def unproxied_stats(self) -> list[tuple[float, float]]: """ Convert the statistics to standard Python data types. @@ -277,7 +277,7 @@ def collect(self) -> None: timestamp = round(time() * milliseconds_per_second) self.stats.append((timestamp, psutil.virtual_memory().percent)) - def unproxied_stats(self) -> List[Tuple[float, float]]: + def unproxied_stats(self) -> list[tuple[float, float]]: """ Convert the statistics to standard Python data types. diff --git a/test/test_shell_logger.py b/test/test_shell_logger.py index bc99e38..131fc6e 100644 --- a/test/test_shell_logger.py +++ b/test/test_shell_logger.py @@ -64,9 +64,11 @@ def shell_logger() -> ShellLogger: measure = ["cpu", "memory", "disk"] kwargs = {"measure": measure, "return_info": True, "interval": 0.1} if os.uname().sysname == "Linux": - kwargs.update( - {"trace": "ltrace", "expression": "setlocale", "summary": True} - ) + kwargs |= { + "trace": "ltrace", + "expression": "setlocale", + "summary": True, + } else: print( f"Warning: uname is not 'Linux': {os.uname()}; ltrace not tested." From ee8fdcce4d36087b640f8303d5f303b62979cbf2 Mon Sep 17 00:00:00 2001 From: "Jason M. Gates" Date: Mon, 2 Dec 2024 12:02:25 -0700 Subject: [PATCH 2/3] docs: Update type hint to match the docs/code Sourcery caught that this should be a sequence of tuples, and not just a single one. --- shell_logger/html_utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell_logger/html_utilities.py b/shell_logger/html_utilities.py index 1819673..248ada8 100644 --- a/shell_logger/html_utilities.py +++ b/shell_logger/html_utilities.py @@ -501,7 +501,7 @@ def time_series_plot( def disk_time_series_plot( - cmd_id: str, data_tuples: tuple[float, float], volume_name: str + cmd_id: str, data_tuples: list[tuple[float, float]], volume_name: str ) -> Iterator[str]: """ Generate a time series plot of disk usage. From f9ec075094fef5032987571d16a68c11917e3672 Mon Sep 17 00:00:00 2001 From: "Jason M. Gates" Date: Mon, 2 Dec 2024 12:06:32 -0700 Subject: [PATCH 3/3] refactor: Use exception instead of return value Sourcery suggested using an exception for this unreachable scenario. --- shell_logger/html_utilities.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shell_logger/html_utilities.py b/shell_logger/html_utilities.py index 248ada8..9bdfd1b 100644 --- a/shell_logger/html_utilities.py +++ b/shell_logger/html_utilities.py @@ -866,6 +866,10 @@ def sgr_8bit_color_to_html(sgr_params: list[str]) -> str: # noqa: PLR0911 Returns: A HTML ``span`` with the appropriate CSS style. + + Raises: + RuntimeError: If the code somehow falls through all the `if` + blocks without returning. This should never happen. """ sgr_256 = int(sgr_params[2]) if len(sgr_params) > 2 else 0 if sgr_256 < 0 or sgr_256 > 255 or not sgr_params: @@ -891,7 +895,8 @@ def sgr_8bit_color_to_html(sgr_params: list[str]) -> str: # noqa: PLR0911 return sgr_4bit_color_and_style_to_html(str(40 + sgr_256)) if sgr_256 < 16: return sgr_4bit_color_and_style_to_html(str(92 + sgr_256)) - return "THIS SHOULD NEVER HAPPEN" + message = "THIS SHOULD NEVER HAPPEN" + raise RuntimeError(message) def sgr_24bit_color_to_html(sgr_params: list[str]) -> str: