From fbbd8f63b023bcc1b83c9b30a97a50ed9c90d207 Mon Sep 17 00:00:00 2001 From: Peter Pfeufer Date: Tue, 2 Jun 2026 18:05:56 +0200 Subject: [PATCH] [CHANGE] Modular providers --- CHANGELOG.md | 3 +- sovtimer/locale/django.pot | 8 +- sovtimer/models.py | 6 +- sovtimer/providers/__init__.py | 3 + sovtimer/providers/applogger.py | 44 +++++++++ sovtimer/{providers.py => providers/esi.py} | 45 +-------- sovtimer/tasks.py | 5 +- sovtimer/tests/test_providers.py | 104 ++++++-------------- sovtimer/views.py | 5 +- 9 files changed, 92 insertions(+), 131 deletions(-) create mode 100644 sovtimer/providers/__init__.py create mode 100644 sovtimer/providers/applogger.py rename sovtimer/{providers.py => providers/esi.py} (88%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85152c4b..80660794 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,12 +46,13 @@ Section Order: ### Fixed -- Class 'Iterable' does not define '__sub__', so the '-' operator cannot be used on +- Class `Iterable` does not define `__sub__`, so the `-` operator cannot be used on its instances ### Changed - Task `QueueOnce` options +- Modular providers ## [4.1.0] - 2026-05-19 diff --git a/sovtimer/locale/django.pot b/sovtimer/locale/django.pot index 075aad67..184ca516 100644 --- a/sovtimer/locale/django.pot +++ b/sovtimer/locale/django.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: AA Sov Timer 4.1.0\n" "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-sov-timer/issues\n" -"POT-Creation-Date: 2026-05-21 19:59+0200\n" +"POT-Creation-Date: 2026-06-02 18:04+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -127,14 +127,14 @@ msgstr "" msgid "Join our team of translators!" msgstr "" -#: sovtimer/views.py:148 +#: sovtimer/views.py:147 msgid "Defenders making progress" msgstr "" -#: sovtimer/views.py:152 +#: sovtimer/views.py:151 msgid "Attackers making progress" msgstr "" -#: sovtimer/views.py:156 +#: sovtimer/views.py:155 msgid "Neither side has made any progress yet" msgstr "" diff --git a/sovtimer/models.py b/sovtimer/models.py index 42039324..5a7045a1 100644 --- a/sovtimer/models.py +++ b/sovtimer/models.py @@ -17,8 +17,8 @@ from allianceauth.services.hooks import get_extension_logger # AA Sovereignty Timer -from sovtimer import __title__ -from sovtimer.providers import AppLogger, ESIHandler +from sovtimer.providers.applogger import AppLogger +from sovtimer.providers.esi import ESIHandler if TYPE_CHECKING: # Third Party @@ -27,7 +27,7 @@ # Alliance Auth from esi.stubs import SovereigntyCampaignsGetItem -logger = AppLogger(my_logger=get_extension_logger(name=__name__), prefix=__title__) +logger = AppLogger(my_logger=get_extension_logger(name=__name__)) class AaSovtimer(models.Model): diff --git a/sovtimer/providers/__init__.py b/sovtimer/providers/__init__.py new file mode 100644 index 00000000..2bac5c40 --- /dev/null +++ b/sovtimer/providers/__init__.py @@ -0,0 +1,3 @@ +""" +Initialize the providers +""" diff --git a/sovtimer/providers/applogger.py b/sovtimer/providers/applogger.py new file mode 100644 index 00000000..560f4dc3 --- /dev/null +++ b/sovtimer/providers/applogger.py @@ -0,0 +1,44 @@ +""" +AppLogger provider +""" + +# Standard Library +import logging + +# AA Sovereignty Timer +from sovtimer import __title__ + + +class AppLogger(logging.LoggerAdapter): + """ + Custom logger adapter that adds a prefix to log messages. + + Taken from the `allianceauth-app-utils` package. + Credits to: Erik Kalkoken + """ + + def __init__(self, my_logger: logging.Logger): + """ + Initializes the AppLogger with a logger and a prefix. + + :param my_logger: Logger instance + :type my_logger: logging.Logger + """ + + super().__init__(my_logger, {}) + + self.prefix = __title__ + + def process(self, msg, kwargs): + """ + Prepares the log message by adding the prefix. + + :param msg: Log message + :type msg: str + :param kwargs: Additional keyword arguments + :type kwargs: dict + :return: Prefixed log message and kwargs + :rtype: tuple + """ + + return f"[{self.prefix}] {msg}", kwargs diff --git a/sovtimer/providers.py b/sovtimer/providers/esi.py similarity index 88% rename from sovtimer/providers.py rename to sovtimer/providers/esi.py index 4f4c908b..6efa89a9 100644 --- a/sovtimer/providers.py +++ b/sovtimer/providers/esi.py @@ -3,7 +3,6 @@ """ # Standard Library -import logging from typing import TYPE_CHECKING, Any # Third Party @@ -20,14 +19,16 @@ __app_name_verbose__, __esi_compatibility_date__, __github_url__, - __title__, __version__, ) +from sovtimer.providers.applogger import AppLogger if TYPE_CHECKING: # Alliance Auth from esi.stubs import AllianceDetail, SovereigntyCampaignsGetItem +logger = AppLogger(get_extension_logger(__name__)) + # ESI client esi = ESIClientProvider( # Use the latest compatibility date, see https://esi.evetech.net/meta/compatibility-dates @@ -230,43 +231,3 @@ def get_sovereignty_systems( force_refresh=force_refresh, return_response=return_response, ) - - -class AppLogger(logging.LoggerAdapter): - """ - Custom logger adapter that adds a prefix to log messages. - - Taken from the `allianceauth-app-utils` package. - Credits to: Erik Kalkoken - """ - - def __init__(self, my_logger: logging.Logger, prefix: str = "Sovereignty Timer"): - """ - Initializes the AppLogger with a logger and a prefix. - - :param my_logger: Logger instance - :type my_logger: logging.Logger - :param prefix: Prefix string to add to log messages - :type prefix: str - """ - - super().__init__(my_logger, {}) - - self.prefix = prefix - - def process(self, msg, kwargs): - """ - Prepares the log message by adding the prefix. - - :param msg: Log message - :type msg: str - :param kwargs: Additional keyword arguments - :type kwargs: dict - :return: Prefixed log message and kwargs - :rtype: tuple - """ - - return f"[{self.prefix}] {msg}", kwargs - - -logger = AppLogger(my_logger=get_extension_logger(name=__name__), prefix=__title__) diff --git a/sovtimer/tasks.py b/sovtimer/tasks.py index 797d9adc..822d6cb6 100644 --- a/sovtimer/tasks.py +++ b/sovtimer/tasks.py @@ -15,12 +15,11 @@ from allianceauth.services.tasks import QueueOnce # AA Sovereignty Timer -from sovtimer import __title__ from sovtimer.constants import Constants from sovtimer.models import Alliance, Campaign, SovereigntyStructure -from sovtimer.providers import AppLogger +from sovtimer.providers.applogger import AppLogger -logger = AppLogger(my_logger=get_extension_logger(name=__name__), prefix=__title__) +logger = AppLogger(my_logger=get_extension_logger(name=__name__)) # Params for all tasks diff --git a/sovtimer/tests/test_providers.py b/sovtimer/tests/test_providers.py index d09d1757..57dbdfb0 100644 --- a/sovtimer/tests/test_providers.py +++ b/sovtimer/tests/test_providers.py @@ -17,7 +17,9 @@ from esi.exceptions import HTTPClientError, HTTPNotModified # AA Sovereignty Timer -from sovtimer.providers import AppLogger, ESIHandler +from sovtimer import __title__ +from sovtimer.providers.applogger import AppLogger +from sovtimer.providers.esi import ESIHandler from sovtimer.tests import BaseTestCase @@ -48,7 +50,7 @@ def test_when_typing_TYPE_CHECKING_true_then_stub_types_are_imported_into_module sys.modules["esi.stubs"] = fake_stubs with patch.object(typing, "TYPE_CHECKING", True): - providers = importlib.import_module("sovtimer.providers") + providers = importlib.import_module("sovtimer.providers.esi") importlib.reload(providers) self.assertTrue(hasattr(providers, "AllianceDetail")) @@ -58,7 +60,7 @@ def test_when_typing_TYPE_CHECKING_true_then_stub_types_are_imported_into_module sys.modules.update(original_sys_modules) try: - importlib.reload(importlib.import_module("sovtimer.providers")) + importlib.reload(importlib.import_module("sovtimer.providers.esi")) except Exception: pass @@ -240,8 +242,8 @@ def test_returns_campaigns_when_esi_returns_data(self): """ with ( - patch("sovtimer.providers.esi", new=MagicMock()), - patch("sovtimer.providers.ESIHandler.result") as mock_result, + patch("sovtimer.providers.esi.esi", new=MagicMock()), + patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"campaign_id": 1}, {"campaign_id": 2}] @@ -264,9 +266,10 @@ def test_raises_exception_when_result_raises(self): """ with ( - patch("sovtimer.providers.esi", new=MagicMock()), + patch("sovtimer.providers.esi.esi", new=MagicMock()), patch( - "sovtimer.providers.ESIHandler.result", side_effect=Exception("Error") + "sovtimer.providers.esi.ESIHandler.result", + side_effect=Exception("Error"), ) as mock_result, ): with self.assertRaises(Exception): @@ -288,9 +291,9 @@ def test_logs_debug_message_when_fetching_campaigns(self): """ with ( - patch("sovtimer.providers.esi", new=MagicMock()), - patch("sovtimer.providers.logger.debug") as mock_logger, - patch("sovtimer.providers.ESIHandler.result") as mock_result, + patch("sovtimer.providers.esi.esi", new=MagicMock()), + patch("sovtimer.providers.esi.logger.debug") as mock_logger, + patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"campaign_id": 1}] @@ -304,8 +307,8 @@ class TestESIHandlerGetAlliancesAllianceId(BaseTestCase): Test the ESIHandler.get_alliances_alliance_id method. """ - @patch("sovtimer.providers.esi", new=MagicMock()) - @patch("sovtimer.providers.ESIHandler.result") + @patch("sovtimer.providers.esi.esi", new=MagicMock()) + @patch("sovtimer.providers.esi.ESIHandler.result") def test_returns_alliance_data_when_operation_succeeds(self, mock_result): """ Test that the method returns alliance data when the ESI operation succeeds. @@ -327,8 +330,8 @@ def test_returns_alliance_data_when_operation_succeeds(self, mock_result): self.assertIn("operation", called_kwargs) self.assertFalse(called_kwargs.get("force_refresh")) - @patch("sovtimer.providers.esi", new=MagicMock()) - @patch("sovtimer.providers.ESIHandler.result") + @patch("sovtimer.providers.esi.esi", new=MagicMock()) + @patch("sovtimer.providers.esi.ESIHandler.result") def test_passes_force_refresh_to_result_operation(self, mock_result): """ Test that the force_refresh parameter is passed correctly to the ESIHandler.result method. @@ -367,8 +370,8 @@ def test_returns_systems_when_esi_returns_data(self): """ with ( - patch("sovtimer.providers.esi", new=MagicMock()), - patch("sovtimer.providers.ESIHandler.result") as mock_result, + patch("sovtimer.providers.esi.esi", new=MagicMock()), + patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"system_id": 1}, {"system_id": 2}] @@ -391,9 +394,10 @@ def test_raises_exception_when_result_raises(self): """ with ( - patch("sovtimer.providers.esi", new=MagicMock()), + patch("sovtimer.providers.esi.esi", new=MagicMock()), patch( - "sovtimer.providers.ESIHandler.result", side_effect=Exception("Error") + "sovtimer.providers.esi.ESIHandler.result", + side_effect=Exception("Error"), ) as mock_result, ): with self.assertRaises(Exception): @@ -413,9 +417,9 @@ def test_logs_debug_message_when_fetching_systems(self): """ with ( - patch("sovtimer.providers.esi", new=MagicMock()), - patch("sovtimer.providers.logger.debug") as mock_logger, - patch("sovtimer.providers.ESIHandler.result") as mock_result, + patch("sovtimer.providers.esi.esi", new=MagicMock()), + patch("sovtimer.providers.esi.logger.debug") as mock_logger, + patch("sovtimer.providers.esi.ESIHandler.result") as mock_result, ): mock_result.return_value = [{"system_id": 1}] @@ -438,62 +442,12 @@ def test_adds_prefix_to_log_message(self): """ logger = logging.getLogger("test_logger") - app_logger = AppLogger(logger, "PREFIX") + app_logger = AppLogger(logger) with self.assertLogs("test_logger", level="INFO") as log: app_logger.info("This is a test message") - self.assertIn("[PREFIX] This is a test message", log.output[0]) - - def test_handles_empty_prefix(self): - """ - Tests that the AppLogger handles an empty prefix correctly. - - :return: - :rtype: - """ - - logger = logging.getLogger("test_logger") - app_logger = AppLogger(logger, "") - - with self.assertLogs("test_logger", level="INFO") as log: - app_logger.info("Message without prefix") - - self.assertIn("Message without prefix", log.output[0]) - - def test_handles_non_string_prefix(self): - """ - Tests that the AppLogger handles a non-string prefix correctly. - - :return: - :rtype: - """ - - logger = logging.getLogger("test_logger") - app_logger = AppLogger(logger, 123) - - with self.assertLogs("test_logger", level="INFO") as log: - app_logger.info("Message with numeric prefix") - - self.assertIn("[123] Message with numeric prefix", log.output[0]) - - def test_handles_special_characters_in_prefix(self): - """ - Tests that the AppLogger handles special characters in the prefix correctly. - - :return: - :rtype: - """ - - logger = logging.getLogger("test_logger") - app_logger = AppLogger(logger, "!@#$%^&*()") - - with self.assertLogs("test_logger", level="INFO") as log: - app_logger.info("Message with special characters in prefix") - - self.assertIn( - "[!@#$%^&*()] Message with special characters in prefix", log.output[0] - ) + self.assertIn(f"[{__title__}] This is a test message", log.output[0]) def test_handles_empty_message(self): """ @@ -504,9 +458,9 @@ def test_handles_empty_message(self): """ logger = logging.getLogger("test_logger") - app_logger = AppLogger(logger, "PREFIX") + app_logger = AppLogger(logger) with self.assertLogs("test_logger", level="INFO") as log: app_logger.info("") - self.assertIn("[PREFIX] ", log.output[0]) + self.assertIn(f"[{__title__}] ", log.output[0]) diff --git a/sovtimer/views.py b/sovtimer/views.py index ba31f0e5..24c4288c 100644 --- a/sovtimer/views.py +++ b/sovtimer/views.py @@ -19,11 +19,10 @@ from allianceauth.services.hooks import get_extension_logger # AA Sovereignty Timer -from sovtimer import __title__ from sovtimer.models import Campaign, SovereigntyStructure -from sovtimer.providers import AppLogger +from sovtimer.providers.applogger import AppLogger -logger = AppLogger(my_logger=get_extension_logger(name=__name__), prefix=__title__) +logger = AppLogger(my_logger=get_extension_logger(name=__name__)) def _fmt_float_to_percentage(value: float) -> str: