Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 5 additions & 0 deletions template_python/constants.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
46 changes: 46 additions & 0 deletions template_python/logging_setup.py
Original file line number Diff line number Diff line change
@@ -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()
36 changes: 36 additions & 0 deletions tests/test_logging_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""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, setup_default_logging


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]


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]
7 changes: 7 additions & 0 deletions tests/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down