From c7fc464f9c5ae814c8ce8e03d97d4aac1ebb2210 Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sat, 31 Aug 2024 23:39:12 +0330 Subject: [PATCH 1/9] :zap::hammer: refactor(validators): Update date_format validator Refactoed date format validator and add custom validation to make it compatible with tests Added date_format_directives in constants --- django_logging/constants/__init__.py | 1 + .../constants/date_format_directives.py | 22 ++++++++++++++++ .../validators/config_validators.py | 25 +++++++++++++------ .../validators/email_settings_validator.py | 7 +++--- 4 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 django_logging/constants/date_format_directives.py diff --git a/django_logging/constants/__init__.py b/django_logging/constants/__init__.py index 8ea139c..260cfc7 100644 --- a/django_logging/constants/__init__.py +++ b/django_logging/constants/__init__.py @@ -1,3 +1,4 @@ +from .date_format_directives import VALID_DIRECTIVES from .default_settings import DefaultConsoleSettings, DefaultLoggingSettings from .log_format_options import FORMAT_OPTIONS from .log_format_specifiers import LOG_FORMAT_SPECIFIERS diff --git a/django_logging/constants/date_format_directives.py b/django_logging/constants/date_format_directives.py new file mode 100644 index 0000000..359cc21 --- /dev/null +++ b/django_logging/constants/date_format_directives.py @@ -0,0 +1,22 @@ +from typing import Set + +VALID_DIRECTIVES: Set = { + "%Y", + "%m", + "%d", + "%H", + "%M", + "%S", + "%a", + "%A", + "%b", + "%B", + "%p", + "%I", + "%j", + "%U", + "%W", + "%c", + "%x", + "%X", +} diff --git a/django_logging/validators/config_validators.py b/django_logging/validators/config_validators.py index e899e2b..611c121 100644 --- a/django_logging/validators/config_validators.py +++ b/django_logging/validators/config_validators.py @@ -1,11 +1,14 @@ import os import re -from datetime import datetime from typing import List from django.core.checks import Error -from django_logging.constants import FORMAT_OPTIONS, LOG_FORMAT_SPECIFIERS +from django_logging.constants import ( + FORMAT_OPTIONS, + LOG_FORMAT_SPECIFIERS, + VALID_DIRECTIVES, +) from django_logging.constants.config_types import ( FormatOption, LogEmailNotifierType, @@ -172,6 +175,10 @@ def validate_boolean_setting(value: bool, config_name: str) -> List[Error]: def validate_date_format(date_format: str, config_name: str) -> List[Error]: + def parse_format_string(format_string: str) -> set: + """Extract format specifiers from the format string.""" + return set(re.findall(r"%[a-zA-Z]", format_string)) + errors = [] if not isinstance(date_format, str): errors.append( @@ -182,13 +189,17 @@ def validate_date_format(date_format: str, config_name: str) -> List[Error]: ) ) else: - try: - datetime.now().strftime(date_format) - except ValueError as e: + # Extract directives from the provided format string + directives = parse_format_string(date_format) + + # Validate against allowed directives + invalid_directives = directives - VALID_DIRECTIVES + if invalid_directives: errors.append( Error( - f"{config_name} is not a valid date format string.", - hint=f"Error: {e}. Refer to Python's strftime directives for valid formats.", + f"{config_name} contains invalid format directives.", + hint=f"Invalid directives: {', '.join(invalid_directives)}." + f"\n Ensure {config_name} follows valid strftime directives.", id=f"django_logging.E016_{config_name}", ) ) diff --git a/django_logging/validators/email_settings_validator.py b/django_logging/validators/email_settings_validator.py index c3d671f..8891678 100644 --- a/django_logging/validators/email_settings_validator.py +++ b/django_logging/validators/email_settings_validator.py @@ -10,10 +10,11 @@ def check_email_settings(require_admin_email: bool = True) -> List[Error]: - """ - Check if all required email settings are present in the settings file. + """Check if all required email settings are present in the settings file. + + Returns a list of errors if any of the required email settings are + missing. - Returns a list of errors if any of the required email settings are missing. """ errors: List[Error] = [] if require_admin_email: From e418030ef2c79fbfbc40c30f7c0306ca5818cffc Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sat, 31 Aug 2024 23:46:39 +0330 Subject: [PATCH 2/9] :zap::hammer::heavy_check_mark: refactor(tests): move tests dir into app Updated tests with refactored validator and make tests pass --- .pre-commit-config.yaml | 1 + {tests => django_logging/tests}/__init__.py | 0 .../tests}/commands/__init__.py | 0 .../tests}/commands/test_send_logs.py | 2 +- {tests => django_logging/tests}/conftest.py | 4 +- {tests => django_logging/tests}/constants.py | 0 .../tests}/filters/__init__.py | 0 .../tests}/filters/test_log_level_filter.py | 2 +- .../tests}/fixtures/__init__.py | 0 .../fixtures/colored_formatter_fixture.py | 0 .../tests}/fixtures/conf_fixture.py | 0 .../tests}/fixtures/email_handler_fixture.py | 0 .../tests}/fixtures/email_notifier_fixture.py | 0 .../tests}/fixtures/email_settings_fixture.py | 0 .../tests}/fixtures/log_and_notify_fixture.py | 0 .../tests}/fixtures/log_record_fixture.py | 0 .../tests}/fixtures/logger_fixture.py | 0 .../fixtures/request_middleware_fixture.py | 0 .../tests}/fixtures/settings_fixture.py | 0 django_logging/tests/fixtures/setup_django.py | 55 +++++++++++++++++++ .../tests}/formatters/__init__.py | 0 .../formatters/test_colored_formatter.py | 2 +- .../tests}/handlers/__init__.py | 0 .../tests}/handlers/test_email_handler.py | 2 +- .../tests}/middleware/__init__.py | 0 .../middleware/test_request_middleware.py | 2 +- .../tests}/settings/__init__.py | 0 .../tests}/settings/test_checks.py | 2 +- .../tests}/settings/test_conf.py | 9 ++- .../tests}/utils/__init__.py | 0 .../tests}/utils/test_context_manager.py | 2 +- .../tests}/utils/test_email_notifier.py | 2 +- .../tests}/utils/test_get_conf.py | 2 +- .../tests}/utils/test_log_and_notify.py | 2 +- .../tests}/utils/test_set_conf.py | 2 +- .../tests}/validators/__init__.py | 0 .../validators/test_config_validators.py | 4 +- .../test_email_settings_validator.py | 2 +- 38 files changed, 79 insertions(+), 18 deletions(-) rename {tests => django_logging/tests}/__init__.py (100%) rename {tests => django_logging/tests}/commands/__init__.py (100%) rename {tests => django_logging/tests}/commands/test_send_logs.py (98%) rename {tests => django_logging/tests}/conftest.py (71%) rename {tests => django_logging/tests}/constants.py (100%) rename {tests => django_logging/tests}/filters/__init__.py (100%) rename {tests => django_logging/tests}/filters/test_log_level_filter.py (96%) rename {tests => django_logging/tests}/fixtures/__init__.py (100%) rename {tests => django_logging/tests}/fixtures/colored_formatter_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/conf_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/email_handler_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/email_notifier_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/email_settings_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/log_and_notify_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/log_record_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/logger_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/request_middleware_fixture.py (100%) rename {tests => django_logging/tests}/fixtures/settings_fixture.py (100%) create mode 100644 django_logging/tests/fixtures/setup_django.py rename {tests => django_logging/tests}/formatters/__init__.py (100%) rename {tests => django_logging/tests}/formatters/test_colored_formatter.py (98%) rename {tests => django_logging/tests}/handlers/__init__.py (100%) rename {tests => django_logging/tests}/handlers/test_email_handler.py (98%) rename {tests => django_logging/tests}/middleware/__init__.py (100%) rename {tests => django_logging/tests}/middleware/test_request_middleware.py (98%) rename {tests => django_logging/tests}/settings/__init__.py (100%) rename {tests => django_logging/tests}/settings/test_checks.py (99%) rename {tests => django_logging/tests}/settings/test_conf.py (94%) rename {tests => django_logging/tests}/utils/__init__.py (100%) rename {tests => django_logging/tests}/utils/test_context_manager.py (98%) rename {tests => django_logging/tests}/utils/test_email_notifier.py (98%) rename {tests => django_logging/tests}/utils/test_get_conf.py (98%) rename {tests => django_logging/tests}/utils/test_log_and_notify.py (99%) rename {tests => django_logging/tests}/utils/test_set_conf.py (98%) rename {tests => django_logging/tests}/validators/__init__.py (100%) rename {tests => django_logging/tests}/validators/test_config_validators.py (99%) rename {tests => django_logging/tests}/validators/test_email_settings_validator.py (98%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55ef99d..1227b2c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,6 +79,7 @@ repos: - "-sn" - "--rcfile=pyproject.toml" files: ^django_logging/ + exclude: (migrations/|tests/|docs/).* ci: skip: [pylint] diff --git a/tests/__init__.py b/django_logging/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to django_logging/tests/__init__.py diff --git a/tests/commands/__init__.py b/django_logging/tests/commands/__init__.py similarity index 100% rename from tests/commands/__init__.py rename to django_logging/tests/commands/__init__.py diff --git a/tests/commands/test_send_logs.py b/django_logging/tests/commands/test_send_logs.py similarity index 98% rename from tests/commands/test_send_logs.py rename to django_logging/tests/commands/test_send_logs.py index d7ef1c5..69d6892 100644 --- a/tests/commands/test_send_logs.py +++ b/django_logging/tests/commands/test_send_logs.py @@ -9,7 +9,7 @@ from django.core.management import call_command from django.test import TestCase -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.commands, diff --git a/tests/conftest.py b/django_logging/tests/conftest.py similarity index 71% rename from tests/conftest.py rename to django_logging/tests/conftest.py index 308db44..e2d6245 100644 --- a/tests/conftest.py +++ b/django_logging/tests/conftest.py @@ -1,4 +1,4 @@ -from tests.fixtures import ( +from django_logging.tests.fixtures import ( admin_email_mock_settings, colored_formatter, debug_log_record, @@ -18,3 +18,5 @@ request_middleware, reset_settings, ) +from django_logging.tests.fixtures.setup_django import configure_django_settings +configure_django_settings() diff --git a/tests/constants.py b/django_logging/tests/constants.py similarity index 100% rename from tests/constants.py rename to django_logging/tests/constants.py diff --git a/tests/filters/__init__.py b/django_logging/tests/filters/__init__.py similarity index 100% rename from tests/filters/__init__.py rename to django_logging/tests/filters/__init__.py diff --git a/tests/filters/test_log_level_filter.py b/django_logging/tests/filters/test_log_level_filter.py similarity index 96% rename from tests/filters/test_log_level_filter.py rename to django_logging/tests/filters/test_log_level_filter.py index a876147..d2a97f3 100644 --- a/tests/filters/test_log_level_filter.py +++ b/django_logging/tests/filters/test_log_level_filter.py @@ -4,7 +4,7 @@ import pytest from django_logging.filters import LoggingLevelFilter -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.filters, diff --git a/tests/fixtures/__init__.py b/django_logging/tests/fixtures/__init__.py similarity index 100% rename from tests/fixtures/__init__.py rename to django_logging/tests/fixtures/__init__.py diff --git a/tests/fixtures/colored_formatter_fixture.py b/django_logging/tests/fixtures/colored_formatter_fixture.py similarity index 100% rename from tests/fixtures/colored_formatter_fixture.py rename to django_logging/tests/fixtures/colored_formatter_fixture.py diff --git a/tests/fixtures/conf_fixture.py b/django_logging/tests/fixtures/conf_fixture.py similarity index 100% rename from tests/fixtures/conf_fixture.py rename to django_logging/tests/fixtures/conf_fixture.py diff --git a/tests/fixtures/email_handler_fixture.py b/django_logging/tests/fixtures/email_handler_fixture.py similarity index 100% rename from tests/fixtures/email_handler_fixture.py rename to django_logging/tests/fixtures/email_handler_fixture.py diff --git a/tests/fixtures/email_notifier_fixture.py b/django_logging/tests/fixtures/email_notifier_fixture.py similarity index 100% rename from tests/fixtures/email_notifier_fixture.py rename to django_logging/tests/fixtures/email_notifier_fixture.py diff --git a/tests/fixtures/email_settings_fixture.py b/django_logging/tests/fixtures/email_settings_fixture.py similarity index 100% rename from tests/fixtures/email_settings_fixture.py rename to django_logging/tests/fixtures/email_settings_fixture.py diff --git a/tests/fixtures/log_and_notify_fixture.py b/django_logging/tests/fixtures/log_and_notify_fixture.py similarity index 100% rename from tests/fixtures/log_and_notify_fixture.py rename to django_logging/tests/fixtures/log_and_notify_fixture.py diff --git a/tests/fixtures/log_record_fixture.py b/django_logging/tests/fixtures/log_record_fixture.py similarity index 100% rename from tests/fixtures/log_record_fixture.py rename to django_logging/tests/fixtures/log_record_fixture.py diff --git a/tests/fixtures/logger_fixture.py b/django_logging/tests/fixtures/logger_fixture.py similarity index 100% rename from tests/fixtures/logger_fixture.py rename to django_logging/tests/fixtures/logger_fixture.py diff --git a/tests/fixtures/request_middleware_fixture.py b/django_logging/tests/fixtures/request_middleware_fixture.py similarity index 100% rename from tests/fixtures/request_middleware_fixture.py rename to django_logging/tests/fixtures/request_middleware_fixture.py diff --git a/tests/fixtures/settings_fixture.py b/django_logging/tests/fixtures/settings_fixture.py similarity index 100% rename from tests/fixtures/settings_fixture.py rename to django_logging/tests/fixtures/settings_fixture.py diff --git a/django_logging/tests/fixtures/setup_django.py b/django_logging/tests/fixtures/setup_django.py new file mode 100644 index 0000000..f54799a --- /dev/null +++ b/django_logging/tests/fixtures/setup_django.py @@ -0,0 +1,55 @@ +from django.conf import settings +import django + + +def configure_django_settings(): + if not settings.configured: + settings.configure( + DEBUG=True, + DATABASES={ + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } + }, + INSTALLED_APPS=[ + "django.contrib.contenttypes", + "django.contrib.auth", + "django_logging", + ], + MIDDLEWARE=[], + DJANGO_LOGGING={ + "AUTO_INITIALIZATION_ENABLE": True, + "INITIALIZATION_MESSAGE_ENABLE": True, + "LOG_FILE_LEVELS": ["DEBUG", "INFO"], + "LOG_DIR": "logs", + "LOG_FILE_FORMATS": { + "DEBUG": 1, + "INFO": 1, + }, + "LOG_CONSOLE_LEVEL": "DEBUG", + "LOG_CONSOLE_FORMAT": 1, + "LOG_CONSOLE_COLORIZE": True, + "LOG_DATE_FORMAT": "%Y-%m-%d %H:%M:%S", + "LOG_EMAIL_NOTIFIER": { + "ENABLE": False, + "NOTIFY_ERROR": True, + "NOTIFY_CRITICAL": False, + "LOG_FORMAT": True, + "USE_TEMPLATE": True, + }, + }, + EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend", + EMAIL_HOST="smtp.example.com", + EMAIL_PORT=587, + EMAIL_USE_TLS=True, + EMAIL_HOST_USER="example@test.com", + EMAIL_HOST_PASSWORD="the_password", + DEFAULT_FROM_EMAIL="example@test.com", + ADMIN_EMAIL="admin@test.com", + ) + django.setup() + + +# Call this function before running your tests +configure_django_settings() diff --git a/tests/formatters/__init__.py b/django_logging/tests/formatters/__init__.py similarity index 100% rename from tests/formatters/__init__.py rename to django_logging/tests/formatters/__init__.py diff --git a/tests/formatters/test_colored_formatter.py b/django_logging/tests/formatters/test_colored_formatter.py similarity index 98% rename from tests/formatters/test_colored_formatter.py rename to django_logging/tests/formatters/test_colored_formatter.py index 091306e..ea930cb 100644 --- a/tests/formatters/test_colored_formatter.py +++ b/django_logging/tests/formatters/test_colored_formatter.py @@ -5,7 +5,7 @@ import pytest from django_logging.formatters import ColoredFormatter -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.formatters, diff --git a/tests/handlers/__init__.py b/django_logging/tests/handlers/__init__.py similarity index 100% rename from tests/handlers/__init__.py rename to django_logging/tests/handlers/__init__.py diff --git a/tests/handlers/test_email_handler.py b/django_logging/tests/handlers/test_email_handler.py similarity index 98% rename from tests/handlers/test_email_handler.py rename to django_logging/tests/handlers/test_email_handler.py index c27c103..2256d1a 100644 --- a/tests/handlers/test_email_handler.py +++ b/django_logging/tests/handlers/test_email_handler.py @@ -6,7 +6,7 @@ from django.conf import settings from django_logging.handlers.email_handler import EmailHandler -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.handlers, diff --git a/tests/middleware/__init__.py b/django_logging/tests/middleware/__init__.py similarity index 100% rename from tests/middleware/__init__.py rename to django_logging/tests/middleware/__init__.py diff --git a/tests/middleware/test_request_middleware.py b/django_logging/tests/middleware/test_request_middleware.py similarity index 98% rename from tests/middleware/test_request_middleware.py rename to django_logging/tests/middleware/test_request_middleware.py index be4b38d..1571881 100644 --- a/tests/middleware/test_request_middleware.py +++ b/django_logging/tests/middleware/test_request_middleware.py @@ -8,7 +8,7 @@ from django.test import RequestFactory from django_logging.middleware import RequestLogMiddleware -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.middleware, diff --git a/tests/settings/__init__.py b/django_logging/tests/settings/__init__.py similarity index 100% rename from tests/settings/__init__.py rename to django_logging/tests/settings/__init__.py diff --git a/tests/settings/test_checks.py b/django_logging/tests/settings/test_checks.py similarity index 99% rename from tests/settings/test_checks.py rename to django_logging/tests/settings/test_checks.py index 99a45c4..326a58d 100644 --- a/tests/settings/test_checks.py +++ b/django_logging/tests/settings/test_checks.py @@ -7,7 +7,7 @@ from django.core.checks import Error from django_logging.settings.checks import check_logging_settings -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.settings, diff --git a/tests/settings/test_conf.py b/django_logging/tests/settings/test_conf.py similarity index 94% rename from tests/settings/test_conf.py rename to django_logging/tests/settings/test_conf.py index 903408b..d1a142b 100644 --- a/tests/settings/test_conf.py +++ b/django_logging/tests/settings/test_conf.py @@ -7,7 +7,7 @@ from django_logging.constants import FORMAT_OPTIONS from django_logging.settings.conf import LogConfig, LogManager -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.settings, @@ -173,7 +173,10 @@ def test_log_manager_get_log_file(self, log_manager: LogManager) -> None: log_manager.create_log_files() - assert log_manager.get_log_file("INFO") == "/tmp/logs\\info.log" - assert log_manager.get_log_file("ERROR") == "/tmp/logs\\error.log" + expected_info_log_path = os.path.join("/tmp/logs", "info.log") + expected_error_log_path = os.path.join("/tmp/logs", "error.log") + + assert log_manager.get_log_file("INFO") == expected_info_log_path + assert log_manager.get_log_file("ERROR") == expected_error_log_path assert log_manager.get_log_file("DEBUG") is None diff --git a/tests/utils/__init__.py b/django_logging/tests/utils/__init__.py similarity index 100% rename from tests/utils/__init__.py rename to django_logging/tests/utils/__init__.py diff --git a/tests/utils/test_context_manager.py b/django_logging/tests/utils/test_context_manager.py similarity index 98% rename from tests/utils/test_context_manager.py rename to django_logging/tests/utils/test_context_manager.py index e174610..578cf9c 100644 --- a/tests/utils/test_context_manager.py +++ b/django_logging/tests/utils/test_context_manager.py @@ -5,7 +5,7 @@ import pytest from django_logging.utils.context_manager import _restore_logging_config, config_setup -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.utils, diff --git a/tests/utils/test_email_notifier.py b/django_logging/tests/utils/test_email_notifier.py similarity index 98% rename from tests/utils/test_email_notifier.py rename to django_logging/tests/utils/test_email_notifier.py index 1efd5ee..b68020e 100644 --- a/tests/utils/test_email_notifier.py +++ b/django_logging/tests/utils/test_email_notifier.py @@ -7,7 +7,7 @@ import pytest from django_logging.utils.log_email_notifier.notifier import send_email_async -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.utils, diff --git a/tests/utils/test_get_conf.py b/django_logging/tests/utils/test_get_conf.py similarity index 98% rename from tests/utils/test_get_conf.py rename to django_logging/tests/utils/test_get_conf.py index 99558ff..6b6df79 100644 --- a/tests/utils/test_get_conf.py +++ b/django_logging/tests/utils/test_get_conf.py @@ -11,7 +11,7 @@ is_initialization_message_enabled, use_email_notifier_template, ) -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.utils, diff --git a/tests/utils/test_log_and_notify.py b/django_logging/tests/utils/test_log_and_notify.py similarity index 99% rename from tests/utils/test_log_and_notify.py rename to django_logging/tests/utils/test_log_and_notify.py index c3a86fa..b2262c4 100644 --- a/tests/utils/test_log_and_notify.py +++ b/django_logging/tests/utils/test_log_and_notify.py @@ -7,7 +7,7 @@ from django.conf import settings from django_logging.utils.log_email_notifier.log_and_notify import log_and_notify_admin -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.utils, diff --git a/tests/utils/test_set_conf.py b/django_logging/tests/utils/test_set_conf.py similarity index 98% rename from tests/utils/test_set_conf.py rename to django_logging/tests/utils/test_set_conf.py index 5aaebb1..072cd76 100644 --- a/tests/utils/test_set_conf.py +++ b/django_logging/tests/utils/test_set_conf.py @@ -6,7 +6,7 @@ from django_logging.constants.ansi_colors import AnsiColors from django_logging.utils.set_conf import set_config -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.utils, diff --git a/tests/validators/__init__.py b/django_logging/tests/validators/__init__.py similarity index 100% rename from tests/validators/__init__.py rename to django_logging/tests/validators/__init__.py diff --git a/tests/validators/test_config_validators.py b/django_logging/tests/validators/test_config_validators.py similarity index 99% rename from tests/validators/test_config_validators.py rename to django_logging/tests/validators/test_config_validators.py index f344c55..ac66b74 100644 --- a/tests/validators/test_config_validators.py +++ b/django_logging/tests/validators/test_config_validators.py @@ -13,7 +13,7 @@ validate_format_string, validate_log_levels, ) -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.validators, @@ -327,7 +327,7 @@ def test_validate_date_format_invalid_format(self) -> None: assert len(errors) == 1 assert errors[0].id == "django_logging.E015_date_format" - date_format = "%invalid" + date_format = "%Q" # invalid format errors = validate_date_format(date_format, "date_format") assert len(errors) == 1 assert errors[0].id == "django_logging.E016_date_format" diff --git a/tests/validators/test_email_settings_validator.py b/django_logging/tests/validators/test_email_settings_validator.py similarity index 98% rename from tests/validators/test_email_settings_validator.py rename to django_logging/tests/validators/test_email_settings_validator.py index c48ad85..6064220 100644 --- a/tests/validators/test_email_settings_validator.py +++ b/django_logging/tests/validators/test_email_settings_validator.py @@ -6,7 +6,7 @@ from django.conf import settings from django_logging.validators.email_settings_validator import check_email_settings -from tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON pytestmark = [ pytest.mark.validators, From a2f2e99cb84066816c11ad7f4508f76a5ef7b0c0 Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sat, 31 Aug 2024 23:48:24 +0330 Subject: [PATCH 3/9] :zap::wrench: chore:Update pyproject.toml config Added updates related to tests path --- pyproject.toml | 268 ++++++++++++++++++++++++------------------------- 1 file changed, 129 insertions(+), 139 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 610fb9f..2971bf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,22 @@ +[build-system] +build-backend = "poetry.core.masonry.api" +requires = [ "poetry-core" ] + [tool.poetry] name = "django-logging" version = "0.1.0" description = "A package for logging in django applications" -authors = ["ARYAN-NIKNEZHAD ", "MEHRSHAD-MIRSHEKARY "] +authors = [ "ARYAN-NIKNEZHAD ", "MEHRSHAD-MIRSHEKARY " ] license = "MIT" readme = "README.md" [tool.poetry.dependencies] python = ">=3.8,<4.0" django = [ - { version = ">=4.2,<5.0", python = ">=3.8,<3.10"}, - { version = ">=4.2,<5.3", python = ">=3.10" } # Django 4.2 and 5.x for Python 3.10+ + { version = ">=4.2,<5.0", python = ">=3.8,<3.10" }, + { version = ">=4.2,<5.3", python = ">=3.10" }, # Django 4.2 and 5.x for Python 3.10+ ] - [tool.poetry.dev-dependencies] pytest = "^8.3.2" pytest-django = "^4.8.0" @@ -25,109 +28,13 @@ isort = "^5.13.2" black = "^24.4.2" commitizen = "^3.28.0" pre-commit = "^3.5.0" -bandit = {extras = ["toml"], version = "^1.7.9"} +bandit = { extras = [ "toml" ], version = "^1.7.9" } tox = "^4.16.0" django-stubs = "^5.0.4" sphinx = "^6.2.1" sphinx-rtd-theme = "^2.0.0" docformatter = "^1.7.5" - -[tool.pytest.ini_options] -DJANGO_SETTINGS_MODULE = "kernel.settings" -python_files = ["tests.py", "test_*.py"] -addopts = "--cov=django_logging --cov-report=html" -markers = [ - "commands: Tests for Django management commands in the logging package.", - "commands_send_logs: Tests focused on the `send_logs` management command.", - "filters: Tests that verify log filtering mechanisms.", - "filters_level_filter: Tests for filtering logs based on their severity level.", - "formatters: Tests for log formatters that structure log messages.", - "colored_formatter: Tests for a formatter that adds color coding to log messages.", - "handlers: Tests for components that dispatch log messages to their destinations.", - "email_handler: Tests for the handler responsible for sending logs via email.", - "middleware: Tests related to middleware components in logging.", - "request_middleware: Tests for middleware handling incoming HTTP requests in logging.", - "settings: Tests that ensure logging settings are correctly applied.", - "settings_checks: Tests for validating the correctness of logging settings.", - "settings_conf: Tests for verifying the configuration of logging settings.", - "utils: Tests for utility functions supporting the logging package.", - "utils_context_manager: Tests for context managers handling resources in logging.", - "utils_email_notifier: Tests for utilities that send notifications via email.", - "utils_get_conf: Tests for utility functions that retrieve configuration settings.", - "utils_log_and_notify: Tests for logging utilities that trigger notifications.", - "utils_set_conf: Tests for utility functions that set configuration settings.", - "validators: Tests for validating configurations and inputs in the logging package.", - "config_validator: Tests focused on validators that check configuration settings.", - "email_settings_validator: Tests for validators that ensure email settings are valid." -] - - - -[tool.pylint] -disable = [ - "C0103", # Invalid constant name - "C0114", # Missing module docstring - "C0115", # Missing class docstring - "C0116", # Missing function or method docstring - "E1101", # Instance of 'Foo' has no 'bar' member - "W0212", # Access to a protected member - "C0301", # Line too long - "C0411", # Wrong import order - "W0611", # Unused imports - "W0613", # Unused arguments - "W0622", # Redefining built-in names - "R0903", # Too few public methods - "R0801", # Duplicate code - "W0621", - "C0415", - "R1719", # The if expression can be replaced with 'bool(test)' - "R1705", # Unnecessary "elif" after "return" - "R0401", -] -max-line-length = 88 -ignore = [ - "migrations/*", - "venv/*", - "build/*", - "dist/*", - ".git/*", - ".tox/*", - "__pycache__/*", - "*.egg-info/*", - ".mypy_cache/*", - ".pytest_cache/*" -] -django-settings-module= "kernel.settings" -load-plugins = [ - "pylint_django", - "pylint.extensions.docparams", -] - -suggestion-mode = true -const-rgx = "([A-Z_][A-Z0-9_]*)|(__.*__)" -attr-rgx = "[a-z_][a-z0-9_]{2,30}$" -variable-rgx = "[a-z_][a-z0-9_]{2,30}$" -argument-rgx = "[a-z_][a-z0-9_]{2,30}$" -method-rgx = "[a-z_][a-z0-9_]{2,30}$" -function-rgx = "[a-z_][a-z0-9_]{2,30}$" -class-rgx = "[A-Z_][a-zA-Z0-9]+$" -module-rgx = "(([a-z_][a-z0-9_]*)|(__.*__))$" - - -[tool.mypy] -mypy_path = "stubs" -disallow_untyped_calls = true -disallow_untyped_defs = true -ignore_missing_imports = true -explicit_package_bases = true -exclude = """ -^docs/source/conf.py| -^build/| -^tests/| -^stubs/| -^kernel/ -""" - +codecov = "^2.1.13" [tool.black] line-length = 88 @@ -151,52 +58,141 @@ exclude = ''' )/ ''' - [tool.isort] profile = "black" line_length = 88 skip = [ - "venv", - ".venv", - ".tox", - "build", - "dist", - ".git", - "__pycache__", - "*.egg-info", - ".mypy_cache", - ".pytest_cache", - "migrations", - "node_modules", - "env", - "kernel" + "venv", + ".venv", + ".tox", + "build", + "dist", + ".git", + "__pycache__", + "*.egg-info", + ".mypy_cache", + ".pytest_cache", + "migrations", + "node_modules", + "env", + "kernel", +] + +[tool.pylint] +disable = [ + "C0103", # Invalid constant name + "C0114", # Missing module docstring + "C0115", # Missing class docstring + "C0116", # Missing function or method docstring + "E1101", # Instance of 'Foo' has no 'bar' member + "W0212", # Access to a protected member + "C0301", # Line too long + "C0411", # Wrong import order + "W0611", # Unused imports + "W0613", # Unused arguments + "W0622", # Redefining built-in names + "R0903", # Too few public methods + "R0801", # Duplicate code + "W0621", + "C0415", + "R1719", # The if expression can be replaced with 'bool(test)' + "R1705", # Unnecessary "elif" after "return" + "R0401", +] +max-line-length = 88 +ignore = [ + "tests", + "migrations/*", + "venv/*", + "build/*", + "dist/*", + ".git/*", + ".tox/*", + "__pycache__/*", + "*.egg-info/*", + ".mypy_cache/*", + ".pytest_cache/*", +] +django-settings-module = "kernel.settings" +load-plugins = [ + "pylint_django", + "pylint.extensions.docparams", ] +suggestion-mode = true +const-rgx = "([A-Z_][A-Z0-9_]*)|(__.*__)" +attr-rgx = "[a-z_][a-z0-9_]{2,30}$" +variable-rgx = "[a-z_][a-z0-9_]{2,30}$" +argument-rgx = "[a-z_][a-z0-9_]{2,30}$" +method-rgx = "[a-z_][a-z0-9_]{2,30}$" +function-rgx = "[a-z_][a-z0-9_]{2,30}$" +class-rgx = "[A-Z_][a-zA-Z0-9]+$" +module-rgx = "(([a-z_][a-z0-9_]*)|(__.*__))$" + +[tool.pytest.ini_options] +python_files = [ "tests.py", "test_*.py" ] +addopts = "--cov --cov-report=term-missing --cov-report=html --cov-fail-under=90" +markers = [ + "commands: Tests for Django management commands in the logging package.", + "commands_send_logs: Tests focused on the `send_logs` management command.", + "filters: Tests that verify log filtering mechanisms.", + "filters_level_filter: Tests for filtering logs based on their severity level.", + "formatters: Tests for log formatters that structure log messages.", + "colored_formatter: Tests for a formatter that adds color coding to log messages.", + "handlers: Tests for components that dispatch log messages to their destinations.", + "email_handler: Tests for the handler responsible for sending logs via email.", + "middleware: Tests related to middleware components in logging.", + "request_middleware: Tests for middleware handling incoming HTTP requests in logging.", + "settings: Tests that ensure logging settings are correctly applied.", + "settings_checks: Tests for validating the correctness of logging settings.", + "settings_conf: Tests for verifying the configuration of logging settings.", + "utils: Tests for utility functions supporting the logging package.", + "utils_context_manager: Tests for context managers handling resources in logging.", + "utils_email_notifier: Tests for utilities that send notifications via email.", + "utils_get_conf: Tests for utility functions that retrieve configuration settings.", + "utils_log_and_notify: Tests for logging utilities that trigger notifications.", + "utils_set_conf: Tests for utility functions that set configuration settings.", + "validators: Tests for validating configurations and inputs in the logging package.", + "config_validator: Tests focused on validators that check configuration settings.", + "email_settings_validator: Tests for validators that ensure email settings are valid.", +] [tool.coverage.run] +source = [ "django_logging" ] omit = [ - "*/migrations/*", - "kernel/*", - "*/apps.py", - "manage.py", + "*/tests/*", + "*/migrations/*", ] [tool.coverage.report] exclude_lines = [ - "pragma: no cover", - "def __repr__", - "if self\\.debug", - "raise AssertionError", - "if 0:", - "if __name__ == .__main__.:" + "pragma: no cover", + "def __repr__", + "if self\\.debug", + "raise AssertionError", + "if 0:", + "if __name__ == .__main__.:", ] +[tool.mypy] +mypy_path = "stubs" +disallow_untyped_calls = true +disallow_untyped_defs = true +ignore_missing_imports = true +explicit_package_bases = true +exclude = """ +^docs/source/conf.py| +^build/| +^tests/| +^stubs/| +^kernel/ +""" [tool.bandit] -targets = ["./django_logging"] +targets = [ "./django_logging" ] exclude_dirs = [ - "tests", - "migrations", + "tests", + "migrations", ] severity = "medium" confidence = "medium" @@ -205,21 +201,15 @@ progress = true reports = true output_format = "screen" output_file = "bandit_report.txt" -include = ["B101", "B102"] -exclude_tests = ["B301", "B302"] +include = [ "B101", "B102" ] +exclude_tests = [ "B301", "B302" ] [tool.bandit.plugins] B104 = { check_typed_list = true } - [tool.commitizen] name = "cz_conventional_commits" version = "0.1.0" [tool.commitizen.settings] -increment_types = ["feat", "fix"] - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +increment_types = [ "feat", "fix" ] From 04b3407ace3844578e05a472349b65923894254c Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sat, 31 Aug 2024 23:53:33 +0330 Subject: [PATCH 4/9] :wrench: chore: Update pre-commit config --- .pre-commit-config.yaml | 53 +++++++++++++++++++++++++++++------------ .readthedocs.yml | 29 ---------------------- 2 files changed, 38 insertions(+), 44 deletions(-) delete mode 100644 .readthedocs.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1227b2c..9b55220 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,6 +2,9 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: + - id: check-toml + - id: check-yaml + files: \.yaml$ - id: trailing-whitespace exclude: (migrations/|tests/|docs/).* - id: end-of-file-fixer @@ -15,6 +18,21 @@ repos: - id: check-docstring-first exclude: (migrations/|tests/|docs/).* + - repo: https://github.com/tox-dev/pyproject-fmt + rev: 2.2.1 + hooks: + - id: pyproject-fmt + + - repo: https://github.com/tox-dev/tox-ini-fmt + rev: 1.3.1 + hooks: + - id: tox-ini-fmt + + - repo: https://github.com/asottile/pyupgrade + rev: v3.17.0 + hooks: + - id: pyupgrade + - repo: https://github.com/pre-commit/mirrors-isort rev: v5.10.1 hooks: @@ -49,6 +67,14 @@ repos: args: ["--in-place", "--recursive", "--blank"] exclude: (migrations/|tests/|docs/).* + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.18.0 + hooks: + - id: blacken-docs + additional_dependencies: + - black==24.4.2 + files: '\.rst$' + - repo: https://github.com/rstcheck/rstcheck rev: "v6.2.4" hooks: @@ -68,18 +94,15 @@ repos: pass_filenames: false always_run: true - - id: pylint - name: pylint - entry: pylint - language: system - types: [python] - require_serial: true - args: - - "-rn" - - "-sn" - - "--rcfile=pyproject.toml" - files: ^django_logging/ - exclude: (migrations/|tests/|docs/).* - -ci: - skip: [pylint] +# - id: pylint +# name: pylint +# entry: pylint +# language: system +# types: [python] +# require_serial: true +# args: +# - "-rn" +# - "-sn" +# - "--rcfile=pyproject.toml" +# files: ^django_logging/ +# exclude: (migrations/|tests/|docs/).* diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 9dbd03e..0000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,29 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the OS, Python version, and other tools -build: - os: ubuntu-22.04 - tools: - python: "3.8" - -# Build documentation with Sphinx -sphinx: - configuration: docs/conf.py - -# Optionally build your docs in additional formats such as PDF and ePub -# formats: -# - pdf -# - epub - - -# Optional but recommended, declare the Python requirements required -# to build your documentation -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - requirements: packages/requirements-dev.txt From 138727b0546a2ecd134c6fe3216b636aca29f8ed Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sun, 1 Sep 2024 00:06:01 +0330 Subject: [PATCH 5/9] :wrench::art: chore: Update tox.ini config --- django_logging/filters/log_level_filter.py | 22 +++---- .../management/commands/send_logs.py | 26 ++++---- .../middleware/request_middleware.py | 24 +++----- django_logging/settings/conf.py | 18 +++--- django_logging/utils/context_manager.py | 8 +-- django_logging/utils/get_conf.py | 19 +++--- django_logging/utils/set_conf.py | 4 +- poetry.lock | 29 ++++++--- tox.ini | 61 +++++++++++-------- 9 files changed, 116 insertions(+), 95 deletions(-) diff --git a/django_logging/filters/log_level_filter.py b/django_logging/filters/log_level_filter.py index b147322..e252616 100644 --- a/django_logging/filters/log_level_filter.py +++ b/django_logging/filters/log_level_filter.py @@ -2,37 +2,37 @@ class LoggingLevelFilter(logging.Filter): - """ - Filters log records based on their logging level. + """Filters log records based on their logging level. + + This filter is used to prevent log records from being written to log + files intended for lower log levels. For example, if we have + separate log files for DEBUG, INFO, WARNING, and ERROR levels, this + filter ensures that a log record with level ERROR is only written to + the ERROR log file, and not to the DEBUG, INFO or WARNING log files. - This filter is used to prevent log records from being written to log files - intended for lower log levels. For example, if we have separate log - files for DEBUG, INFO, WARNING, and ERROR levels, this filter ensures that - a log record with level ERROR is only written to the ERROR log file, and not - to the DEBUG, INFO or WARNING log files. """ def __init__(self, logging_level: int): - """ - Initializes a LoggingLevelFilter instance. + """Initializes a LoggingLevelFilter instance. Args: logging_level: The logging level to filter on (e.g. logging.DEBUG, logging.INFO, etc.). Returns: None + """ super().__init__() self.logging_level = logging_level def filter(self, record: logging.LogRecord) -> bool: - """ - Filters a log record based on its level. + """Filters a log record based on its level. Args: record: The log record to filter. Returns: True if the log record's level matches the specified logging level, False otherwise. + """ return record.levelno == self.logging_level diff --git a/django_logging/management/commands/send_logs.py b/django_logging/management/commands/send_logs.py index fb768be..d051e49 100644 --- a/django_logging/management/commands/send_logs.py +++ b/django_logging/management/commands/send_logs.py @@ -18,35 +18,35 @@ class Command(BaseCommand): - """ - A Django management command that zips the log directory and sends it to + """A Django management command that zips the log directory and sends it to the specified email address. - This command is used to send the log files to a specified email address. - It zips the log directory, creates an email with the zipped file as an attachment, - and sends it to the specified email address. + This command is used to send the log files to a specified email + address. It zips the log directory, creates an email with the zipped + file as an attachment, and sends it to the specified email address. + """ help = "Send log folder to the specified email address" def add_arguments(self, parser: ArgumentParser) -> None: - """ - Add custom command arguments. + """Add custom command arguments. Parameters: parser (ArgumentParser): The argument parser to add arguments to. + """ parser.add_argument( "email", type=str, help="The email address to send the logs to" ) def handle(self, *args: Tuple, **kwargs: Dict) -> None: - """ - The main entry point for the command. + """The main entry point for the command. Parameters: args (tuple): Positional arguments. kwargs (dict): Keyword arguments. + """ email = kwargs["email"] @@ -98,10 +98,12 @@ def handle(self, *args: Tuple, **kwargs: Dict) -> None: logger.info("Temporary zip file cleaned up successfully.") def validate_email_settings(self) -> None: - """ - Check if all required email settings are present in the settings file. + """Check if all required email settings are present in the settings + file. + + Raises ImproperlyConfigured if any of the required email + settings are missing. - Raises ImproperlyConfigured if any of the required email settings are missing. """ errors = check_email_settings(require_admin_email=False) if errors: diff --git a/django_logging/middleware/request_middleware.py b/django_logging/middleware/request_middleware.py index cd5e8ed..acf2293 100644 --- a/django_logging/middleware/request_middleware.py +++ b/django_logging/middleware/request_middleware.py @@ -8,33 +8,33 @@ class RequestLogMiddleware: - """ - Middleware to log information about each incoming request. + """Middleware to log information about each incoming request. + + This middleware logs the request path, the user making the request + (if authenticated), and the user's IP address. - This middleware logs the request path, the user making the request (if authenticated), - and the user's IP address. """ def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None: - """ - Initializes the RequestLogMiddleware instance. + """Initializes the RequestLogMiddleware instance. Args: get_response: A callable that returns an HttpResponse object. + """ self.get_response = get_response user_model = get_user_model() self.username_field = user_model.USERNAME_FIELD def __call__(self, request: HttpRequest) -> HttpResponse: - """ - Processes an incoming request and logs relevant information. + """Processes an incoming request and logs relevant information. Args: request: The incoming request object. Returns: The response object returned by the view function. + """ # Before view (and later middleware) are called. response = self.get_response(request) @@ -67,9 +67,7 @@ def __call__(self, request: HttpRequest) -> HttpResponse: @staticmethod def get_ip_address(request: HttpRequest) -> str: - """ - Retrieves the client's IP address from the request object. - """ + """Retrieves the client's IP address from the request object.""" ip_address = request.META.get("HTTP_X_FORWARDED_FOR") if ip_address: ip_address = ip_address.split(",")[0] @@ -82,7 +80,5 @@ def get_ip_address(request: HttpRequest) -> str: @staticmethod def get_user_agent(request: HttpRequest) -> str: - """ - Retrieves the client's user agent from the request object. - """ + """Retrieves the client's user agent from the request object.""" return request.META.get("HTTP_USER_AGENT", "Unknown User Agent") diff --git a/django_logging/settings/conf.py b/django_logging/settings/conf.py index 38344e2..05185cc 100644 --- a/django_logging/settings/conf.py +++ b/django_logging/settings/conf.py @@ -18,12 +18,12 @@ # pylint: disable=too-many-instance-attributes, too-many-arguments class LogConfig: - """ - Configuration class for django_logging. + """Configuration class for django_logging. Attributes: log_levels (List[str]): A list of log levels to be used in logging. log_dir (str): The directory where log files will be stored. + """ def __init__( @@ -39,7 +39,6 @@ def __init__( log_email_notifier_log_levels: NotifierLogLevels, log_email_notifier_log_format: FormatOption, ) -> None: - self.log_levels = log_levels self.log_dir = log_dir self.log_file_formats = self._resolve_file_formats(log_file_formats) @@ -76,9 +75,7 @@ def _resolve_file_formats(self, log_file_formats: LogFileFormatsType) -> Dict: @staticmethod def remove_ansi_escape_sequences(log_message: str) -> str: - """ - Remove ANSI escape sequences from log messages. - """ + """Remove ANSI escape sequences from log messages.""" import re ansi_escape = re.compile(r"(?:\x1B[@-_][0-?]*[ -/]*[@-~])") @@ -104,16 +101,15 @@ def resolve_format(_format: FormatOption, use_colors: bool = False) -> str: class LogManager: - """ - Manages the creation and configuration of log files. + """Manages the creation and configuration of log files. Attributes: log_config (LogConfig): The logging configuration. log_files (Dict[str, str]): A dictionary mapping log levels to file paths. + """ def __init__(self, log_config: LogConfig) -> None: - self.log_config = log_config self.log_files: Dict[str, str] = {} @@ -130,14 +126,14 @@ def create_log_files(self) -> None: self.log_files[log_level] = log_file_path def get_log_file(self, log_level: LogLevel) -> Optional[str]: - """ - Retrieves the file path for a given log level. + """Retrieves the file path for a given log level. Args: log_level (str): The log level to retrieve the file for. Returns: Optional[str]: The file path associated with the log level, or None if not found. + """ return self.log_files.get(log_level) diff --git a/django_logging/utils/context_manager.py b/django_logging/utils/context_manager.py index 278dc16..62cca92 100644 --- a/django_logging/utils/context_manager.py +++ b/django_logging/utils/context_manager.py @@ -8,14 +8,14 @@ @contextmanager def config_setup() -> Iterator[LogManager]: - """ - Context manager to temporarily apply a custom logging configuration. + """Context manager to temporarily apply a custom logging configuration. Raises: ValueError: If 'AUTO_INITIALIZATION_ENABLE' in DJNAGO_LOGGING is set to True. Yields: LogManager: The log manager instance with the custom configuration. + """ if is_auto_initialization_enabled(): raise ValueError( @@ -47,14 +47,14 @@ def _restore_logging_config( original_level: int, original_handlers: list, ) -> None: - """ - Restore the original logging configuration. + """Restore the original logging configuration. Args: logger (Logger): The root logger instance. original_config (Dict[str, Logger | PlaceHolder]): The original logger dictionary. original_level (int): The original root logger level. original_handlers (list): The original root logger handlers. + """ logger.manager.loggerDict.clear() logger.manager.loggerDict.update(original_config) diff --git a/django_logging/utils/get_conf.py b/django_logging/utils/get_conf.py index 754944c..0b962ce 100644 --- a/django_logging/utils/get_conf.py +++ b/django_logging/utils/get_conf.py @@ -8,11 +8,11 @@ # pylint: disable=too-many-locals def get_config(extra_info: bool = False) -> Dict: - """ - Retrieve logging configuration from Django settings. + """Retrieve logging configuration from Django settings. Returns: A Dict containing all necessary configurations for logging. + """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) logging_defaults = DefaultLoggingSettings() @@ -67,11 +67,12 @@ def get_config(extra_info: bool = False) -> Dict: def use_email_notifier_template() -> bool: - """ - Check whether the email notifier should use a template based on Django settings. + """Check whether the email notifier should use a template based on Django + settings. Returns: bool: True if the email notifier should use a template, False otherwise. + """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) defaults = DefaultLoggingSettings() @@ -83,12 +84,13 @@ def use_email_notifier_template() -> bool: def is_auto_initialization_enabled() -> bool: - """ - Check if the AUTO_INITIALIZATION_ENABLE for the logging system is set to True in Django settings. + """Check if the AUTO_INITIALIZATION_ENABLE for the logging system is set to + True in Django settings. Returns: bool: True if AUTO_INITIALIZATION_ENABLE, False otherwise. Defaults to True if not specified. + """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) defaults = DefaultLoggingSettings() @@ -99,12 +101,13 @@ def is_auto_initialization_enabled() -> bool: def is_initialization_message_enabled() -> bool: - """ - Check if the INITIALIZATION_MESSAGE_ENABLE is set to True in Django settings. + """Check if the INITIALIZATION_MESSAGE_ENABLE is set to True in Django + settings. Returns: bool: True if INITIALIZATION_MESSAGE_ENABLE is True, False otherwise. Defaults to True if not specified. + """ log_settings = getattr(settings, "DJANGO_LOGGING", {}) defaults = DefaultLoggingSettings() diff --git a/django_logging/utils/set_conf.py b/django_logging/utils/set_conf.py index 34c6d06..6cffa65 100644 --- a/django_logging/utils/set_conf.py +++ b/django_logging/utils/set_conf.py @@ -32,8 +32,7 @@ def set_config( log_email_notifier_log_levels: NotifierLogLevels, log_email_notifier_log_format: FormatOption, ) -> None: - """ - Sets up the logging configuration based on the provided parameters. + """Sets up the logging configuration based on the provided parameters. This function initializes and configures logging for the application, including file-based logging, console output, and optional email notifications. @@ -74,6 +73,7 @@ def set_config( Notes: - The function performs system checks and logs warnings if configuration issues are detected. - It also logs the current logging setup upon successful initialization. + """ if not is_auto_initialization_enabled(): return diff --git a/poetry.lock b/poetry.lock index e0973bd..30fed7a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -185,13 +185,13 @@ files = [ [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -329,6 +329,21 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "codecov" +version = "2.1.13" +description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5"}, + {file = "codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c"}, +] + +[package.dependencies] +coverage = "*" +requests = ">=2.7.9" + [[package]] name = "colorama" version = "0.4.6" @@ -1020,13 +1035,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" -version = "3.2.6" +version = "3.2.7" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.2.6-py3-none-any.whl", hash = "sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f"}, - {file = "pylint-3.2.6.tar.gz", hash = "sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3"}, + {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, + {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, ] [package.dependencies] @@ -1657,4 +1672,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "5920aefc57e8a2dc2b8fa514a65ab174f716e598d99d54e3a02e83245e338374" +content-hash = "0fe240ee8025de3e00f6f3dbe0a27bf6a5c193d78fb22d86d617d16bdb4d0e28" diff --git a/tox.ini b/tox.ini index 0bc06f5..98474b8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,43 +1,52 @@ [tox] -envlist = - py38-django40, py39-django40, py310-django40, py311-django40, py312-django40, - py310-django50, py311-django50, py312-django50, - py310-django51, py311-django51, py312-django51, - - -[gh-actions] -python = - 3.8: py38 - 3.9: py39 - 3.10: py310 - 3.11: py311 - 3.12: py312 +requires = + tox>=4.2 +env_list = + py312-django40 + py312-django50 + py312-django51 + py311-django40 + py311-django50 + py311-django51 + py310-django40 + py310-django50 + py310-django51 + py39-django40 + py38-django40 [testenv] description = Run Pytest tests with multiple django versions -develop = True deps = - django40: django>=4.2,<5.0 - django50: django>=5.0,<5.1 - django51: django>=5.1,<5.2 pytest - pytest-django pytest-cov -commands = pytest --cov=django_logging --cov-report=html - -setenv = - DJANGO_SETTINGS_MODULE = kernel.settings - + pytest-django + django40: django<5.0,>=4.2 + django50: django<5.1,>=5 + django51: django<5.2,>=5.1 +commands = + pytest --cov=django_logging --cov-report=html +develop = True [testenv:bandit] description = Run security checks skip_install = true -deps = bandit -commands = bandit -r django_logging +deps = + bandit +commands = + bandit -r django_logging [testenv:pre-commit] description = Run pre-commit hooks skip_install = true -deps = pre-commit +deps = + pre-commit commands = pre-commit run --all-files + +[gh-actions] +python = + 3.8: py38 + 3.9: py39 + 3.10: py310 + 3.11: py311 + 3.12: py312 From 2e9606c79116a689c0841fa645166030509aadcc Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sun, 1 Sep 2024 00:16:17 +0330 Subject: [PATCH 6/9] :heavy_plus_sign::green_heart: chore: Update CI config Updated pytest job and get coverage xml Updated install dependencies and Added poetry installation Ensured all jobs pass successfully Closes #60 --- .github/workflows/ci.yml | 49 ++++++++++++++--------------------- packages/requirements-dev.txt | 2 ++ 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29868ee..88a9fbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,39 +6,30 @@ jobs: test: runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.8, 3.9, 3.10, 3.11, 3.12] - django-version: [4.2, 5.0, 5.1] - steps: - - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} + - uses: actions/checkout@v4 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install poetry tox - poetry install - pip install coverage codecov pytest + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' - - name: Run tests with coverage - run: coverage run -m pytest + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install coverage codecov pytest poetry + pip install -r packages/requirements-dev.txt - - name: Generate coverage report - run: coverage xml + - name: Run tests with coverage + run: pytest --cov=django_logging --cov-report=xml - - name: Run Tox tests - run: tox + - name: Run Tox tests + run: tox - - name: Run pre-commit hooks - run: tox -e pre-commit + - name: Run pre-commit hooks + run: tox -e pre-commit - - name: Upload coverage to Codecov - run: codecov - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Upload coverage to Codecov + run: codecov + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/packages/requirements-dev.txt b/packages/requirements-dev.txt index 3cddcf5..09d5cc2 100644 --- a/packages/requirements-dev.txt +++ b/packages/requirements-dev.txt @@ -12,8 +12,10 @@ cfgv==3.4.0 ; python_version >= "3.8" and python_version < "4.0" chardet==5.2.0 ; python_version >= "3.8" and python_version < "4.0" charset-normalizer==3.3.2 ; python_version >= "3.8" and python_version < "4.0" click==8.1.7 ; python_version >= "3.8" and python_version < "4.0" +codecov==2.1.13 ; python_version >= "3.8" and python_version < "4.0" colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0" commitizen==3.29.0 ; python_version >= "3.8" and python_version < "4.0" +coverage==7.6.1 ; python_version >= "3.8" and python_version < "4.0" coverage[toml]==7.6.1 ; python_version >= "3.8" and python_version < "4.0" decli==0.6.2 ; python_version >= "3.8" and python_version < "4.0" dill==0.3.8 ; python_version >= "3.8" and python_version < "4.0" From 42cec0e880da09da6c2c7204a89d2a0f6903081d Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sun, 1 Sep 2024 00:22:32 +0330 Subject: [PATCH 7/9] :zap: Rename readthedocs yaml file --- .readthedocs.yaml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..9dbd03e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,29 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version, and other tools +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +# Build documentation with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: packages/requirements-dev.txt From c5f6139cfa21fab4a3af1c9dd92ca0a953ac888f Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sun, 1 Sep 2024 00:24:52 +0330 Subject: [PATCH 8/9] :hammer::zap: refactor(docs): Update rst docs style and Improve structure --- docs/quick_start.rst | 2 +- docs/rules.rst | 2 +- docs/settings.rst | 29 ++++++++++++++++++++++------- docs/usage.rst | 14 +++++++------- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/docs/quick_start.rst b/docs/quick_start.rst index ae7f0cc..94c2c88 100644 --- a/docs/quick_start.rst +++ b/docs/quick_start.rst @@ -15,7 +15,7 @@ Getting Started with `django_logging` is simple. Follow these steps to get up an Add `django_logging` to your `INSTALLED_APPS` in your Django settings file: - .. code-block:: python + .. code-block:: INSTALLED_APPS = [ ... diff --git a/docs/rules.rst b/docs/rules.rst index 22f03a5..8e029d8 100644 --- a/docs/rules.rst +++ b/docs/rules.rst @@ -26,7 +26,7 @@ Available Roles **Example Configuration:** - .. code-block:: python + .. code-block:: MIDDLEWARE = [ ... diff --git a/docs/settings.rst b/docs/settings.rst index a0aac53..2fa4672 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -28,28 +28,43 @@ Example configuration: "NOTIFY_ERROR": False, "NOTIFY_CRITICAL": False, "LOG_FORMAT": 1, - "USE_TEMPLATE": True - } + "USE_TEMPLATE": True, + }, } Here's a breakdown of the available configuration options: - **AUTO_INITIALIZATION_ENABLE**: Accepts `bool`. Enables automatic initialization of logging configurations. Defaults to `True`. + - **INITIALIZATION_MESSAGE_ENABLE**: Accepts `bool`. Enables logging of the initialization message. Defaults to `True`. + - **LOG_FILE_LEVELS**: Accepts a list of valid log levels (a list of `str` where each value must be one of the valid levels). Defines the log levels for file logging. Defaults to `['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']`. + - **LOG_DIR**: Accepts `str` like `"path/to/logs"` or a path using functions like `os.path.join()`. Specifies the directory where log files will be stored. Defaults to `"logs"`. + - **LOG_FILE_FORMATS**: Accepts log levels as keys and format options as values. The format option can be an `int` chosen from predefined options or a user-defined format `str`. Defines the format for log files. Defaults to `1` for all levels. + - **Note**:See the **Available Format Options** below for available formats. + - **LOG_CONSOLE_LEVEL**: Accepts `str` that is a valid log level. Specifies the log level for console output. Defaults to `'DEBUG'`, + - **LOG_CONSOLE_FORMAT**: Accepts the same options as `LOG_FILE_FORMATS`. Defines the format for console output. Defaults to `1`. + - **LOG_CONSOLE_COLORIZE**: Accepts `bool`. Determines whether to colorize console output. Defaults to `True`. + - **LOG_DATE_FORMAT**: Accepts `str` that is a valid datetime format. Specifies the date format for log messages. Defaults to `'%Y-%m-%d %H:%M:%S'`. + - **LOG_EMAIL_NOTIFIER**: Is a dictionary where: + - **ENABLE**: Accepts `bool`. Determines whether the email notifier is enabled. Defaults to `False`. + - **NOTIFY_ERROR**: Accepts `bool`. Determines whether to notify on error logs. Defaults to `False`. + - **NOTIFY_CRITICAL**: Accepts `bool`. Determines whether to notify on critical logs. Defaults to `False`. + - **LOG_FORMAT**: Accepts the same options as other log formats (`int` or `str`). Defines the format for log messages sent via email. Defaults to `1`. + - **USE_TEMPLATE**: Accepts `bool`. Determines whether the email includes an HTML template. Defaults to `True`. @@ -99,12 +114,12 @@ Below is an example configuration for the email settings in your `settings.py`: .. code-block:: python - EMAIL_HOST = 'smtp.example.com' + EMAIL_HOST = "smtp.example.com" EMAIL_PORT = 587 - EMAIL_HOST_USER = 'your-email@example.com' - EMAIL_HOST_PASSWORD = 'your-password' + EMAIL_HOST_USER = "your-email@example.com" + EMAIL_HOST_PASSWORD = "your-password" EMAIL_USE_TLS = True - DEFAULT_FROM_EMAIL = 'your-email@example.com' - ADMIN_EMAIL = 'admin@example.com' + DEFAULT_FROM_EMAIL = "your-email@example.com" + ADMIN_EMAIL = "admin@example.com" These settings ensure that the email notifier is correctly configured to send log notifications to the specified `ADMIN_EMAIL` address. diff --git a/docs/usage.rst b/docs/usage.rst index 020a11a..e9d741c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -25,7 +25,7 @@ Once `django_logging` is installed and added to your `INSTALLED_APPS`, you can s To capture and log information of each request to the server, such as the request path, user, IP address, and user agent, add `django_logging.middleware.RequestLogMiddleware` to your `MIDDLEWARE` setting: - .. code-block:: python + .. code-block:: MIDDLEWARE = [ ... @@ -53,11 +53,13 @@ Once `django_logging` is installed and added to your `INSTALLED_APPS`, you can s logger = logging.getLogger(__name__) + def foo(): - logger.info("This log will use the configuration set in the context manager!") + logger.info("This log will use the configuration set in the context manager!") + with config_setup(): - """ Your logging configuration changes here""" + """Your logging configuration changes here""" foo() @@ -89,12 +91,10 @@ Once `django_logging` is installed and added to your `INSTALLED_APPS`, you can s logger = logging.getLogger(__name__) + def some_view(request): log_and_notify_admin( - logger, - logging.INFO, - "This is a log message", - extra={"request": request} + logger, logging.INFO, "This is a log message", extra={"request": request} ) Note: To use the email notifier, `LOG_EMAIL_NOTIFIER["ENABLE"]` must be set to `True`. If it is not enabled, calling `log_and_notify_admin` will raise a `ValueError`: From 48a6397342139793adf48051e186775ce21f8149 Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sun, 1 Sep 2024 00:30:56 +0330 Subject: [PATCH 9/9] :zap::books: Update(docs) readme.md file typo --- .gitignore | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3221e55..187779e 100644 --- a/.gitignore +++ b/.gitignore @@ -139,4 +139,4 @@ uwsgi.ini nginx.conf test_app/ myenv/ -myenv3_8/ \ No newline at end of file +myenv3_8/ diff --git a/README.md b/README.md index 96fa5db..68bd348 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The [`django_logging`](https://github.com/ARYAN-NIKNEZHAD/django_logging) is a D - Language: Python > 3.8 - Framework: Django > 4.2 -- + ## Documentation