From a18f8fa041203a2cf92746f2690764a89f7af58b Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Sun, 12 Apr 2026 21:56:57 +0100 Subject: [PATCH 1/4] Add logging setup and unit tests for logging handlers --- template_python/constants.py | 5 ++++ template_python/logging_setup.py | 46 ++++++++++++++++++++++++++++++++ tests/test_logging_setup.py | 25 +++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 template_python/logging_setup.py create mode 100644 tests/test_logging_setup.py diff --git a/template_python/constants.py b/template_python/constants.py index dfaac73..36c0b02 100644 --- a/template_python/constants.py +++ b/template_python/constants.py @@ -1,5 +1,10 @@ """Constants for codebases using this template.""" +# Logging constants +LOGGING_FORMAT = "[%(asctime)s] %(levelname)s [%(module)s]: %(message)s" +LOGGING_DATE_FORMAT = "%d/%m/%Y | %H:%M:%S" +LOGGING_LEVEL = "INFO" + # Sphinx SPHINX_EXTENSIONS = [ "sphinx.ext.autodoc", # Auto-generate API docs from docstrings diff --git a/template_python/logging_setup.py b/template_python/logging_setup.py new file mode 100644 index 0000000..d0c115a --- /dev/null +++ b/template_python/logging_setup.py @@ -0,0 +1,46 @@ +"""Logging setup for the server.""" + +import logging +import sys +from logging.handlers import RotatingFileHandler +from pathlib import Path + +from template_python.constants import ( + LOGGING_DATE_FORMAT, + LOGGING_FORMAT, + LOGGING_LEVEL, +) + +FORMATTER = logging.Formatter(LOGGING_FORMAT, datefmt=LOGGING_DATE_FORMAT) +_root_logger = logging.getLogger() + + +def add_console_handler() -> None: + """Add a console handler to the root logger.""" + _console_handler = logging.StreamHandler(sys.stdout) + _console_handler.setLevel(getattr(logging, LOGGING_LEVEL)) + _console_handler.setFormatter(FORMATTER) + _root_logger.addHandler(_console_handler) + + +def add_file_handler(logging_filepath: Path, max_bytes: int, backup_count: int) -> None: + """Configure logging with both console and rotating file handlers. + + :param Path logging_filepath: The path to the log file. + :param int max_bytes: The maximum size of the log file in bytes before rotation. + :param int backup_count: The number of backup log files to keep. + """ + logging_filepath.parent.mkdir(exist_ok=True) + _file_handler = RotatingFileHandler( + filename=logging_filepath, maxBytes=max_bytes, backupCount=backup_count, encoding="utf-8" + ) + _file_handler.setLevel(getattr(logging, LOGGING_LEVEL)) + _file_handler.setFormatter(FORMATTER) + _root_logger.addHandler(_file_handler) + + +def setup_default_logging() -> None: + """Configure default logging to console with the specified format and level.""" + _root_logger.setLevel(getattr(logging, LOGGING_LEVEL)) + _root_logger.handlers.clear() + add_console_handler() diff --git a/tests/test_logging_setup.py b/tests/test_logging_setup.py new file mode 100644 index 0000000..893bb1d --- /dev/null +++ b/tests/test_logging_setup.py @@ -0,0 +1,25 @@ +"""Unit tests for the python_template_server.logging_setup module.""" + +import logging +from unittest.mock import MagicMock + +from template_python.logging_setup import add_console_handler, add_file_handler + + +class TestAddHandlers: + """Tests for the add_console_handler and add_file_handler functions.""" + + def test_add_console_handler(self) -> None: + """Test that add_console_handler adds a StreamHandler to the root logger.""" + add_console_handler() + + root_logger = logging.getLogger() + assert "StreamHandler" in [handler.__class__.__name__ for handler in root_logger.handlers] + + def test_add_file_handler(self, tmp_path: MagicMock) -> None: + """Test that add_file_handler adds a RotatingFileHandler to the root logger.""" + log_filepath = tmp_path / "test.log" + add_file_handler(log_filepath, max_bytes=1024, backup_count=1) + + root_logger = logging.getLogger() + assert "RotatingFileHandler" in [handler.__class__.__name__ for handler in root_logger.handlers] From 5e68f9d8ab1225e66fffdc642e8fb536da502237 Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Sun, 12 Apr 2026 21:58:37 +0100 Subject: [PATCH 2/4] Add tests for setup_default_logging function in logging setup --- tests/test_logging_setup.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_logging_setup.py b/tests/test_logging_setup.py index 893bb1d..5f8ca9c 100644 --- a/tests/test_logging_setup.py +++ b/tests/test_logging_setup.py @@ -3,7 +3,7 @@ import logging from unittest.mock import MagicMock -from template_python.logging_setup import add_console_handler, add_file_handler +from template_python.logging_setup import add_console_handler, add_file_handler, setup_default_logging class TestAddHandlers: @@ -23,3 +23,14 @@ def test_add_file_handler(self, tmp_path: MagicMock) -> None: root_logger = logging.getLogger() assert "RotatingFileHandler" in [handler.__class__.__name__ for handler in root_logger.handlers] + + +class TestSetupDefaultLogging: + """Tests for the setup_default_logging function.""" + + def test_setup_default_logging(self) -> None: + """Test that setup_default_logging configures the root logger with a StreamHandler.""" + setup_default_logging() + + root_logger = logging.getLogger() + assert "StreamHandler" in [handler.__class__.__name__ for handler in root_logger.handlers] From ae20e65c7d2a5bb7fb493cfbaa1918779173693b Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Sun, 12 Apr 2026 22:00:11 +0100 Subject: [PATCH 3/4] Add test for generating RST prolog with mismatched keys and values --- tests/test_workflows.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 53b7487..9d14230 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -83,6 +83,13 @@ def test_get_rst_prolog(self) -> None: expected_prolog = ".. |key1| replace:: value1\n.. |key2| replace:: value2" assert prolog == expected_prolog + def test_get_rst_prolog_mismatched_keys_values(self) -> None: + """Test generating an RST prolog with mismatched keys and values.""" + keys = ["key1", "key2"] + values = ["value1"] + with pytest.raises(ValueError, match=r"Keys and values must have the same length."): + get_rst_prolog(keys, values) + def test_print_version_pyproject(self) -> None: """Test printing the version from `pyproject.toml`.""" print_version_pyproject() From efc1f9ad55a760f7ac455fa2f4e8ff1cb0c29f0c Mon Sep 17 00:00:00 2001 From: Javid Ahmed Date: Sun, 12 Apr 2026 22:00:38 +0100 Subject: [PATCH 4/4] Increase coverage threshold to 95% --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index adfd75c..608d83c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ branch = true source = ["template_python", "tests"] [tool.coverage.report] -fail_under = 80 +fail_under = 95 skip_covered = false show_missing = true