diff --git a/allianceauth/authentication/task_statistics/event_series.py b/allianceauth/authentication/task_statistics/event_series.py index a5860598..aead7c6a 100644 --- a/allianceauth/authentication/task_statistics/event_series.py +++ b/allianceauth/authentication/task_statistics/event_series.py @@ -5,7 +5,7 @@ from typing import List, Optional from pytz import utc from redis import Redis, RedisError -from django_redis import get_redis_connection +from allianceauth.utils.cache import get_redis_client logger = logging.getLogger(__name__) @@ -39,7 +39,7 @@ class EventSeries: _ROOT_KEY = "ALLIANCEAUTH_EVENT_SERIES" def __init__(self, key_id: str, redis: Redis = None) -> None: - self._redis = get_redis_connection("default") if not redis else redis + self._redis = get_redis_client() if not redis else redis try: if not self._redis.ping(): raise RuntimeError() diff --git a/allianceauth/authentication/task_statistics/tests/test_event_series.py b/allianceauth/authentication/task_statistics/tests/test_event_series.py index 655b3c88..70804cf4 100644 --- a/allianceauth/authentication/task_statistics/tests/test_event_series.py +++ b/allianceauth/authentication/task_statistics/tests/test_event_series.py @@ -18,7 +18,7 @@ MODULE_PATH = "allianceauth.authentication.task_statistics.event_series" class TestEventSeries(TestCase): def test_should_abort_without_redis_client(self): # when - with patch(MODULE_PATH + ".get_redis_connection") as mock: + with patch(MODULE_PATH + ".get_redis_client") as mock: mock.return_value = None events = EventSeries("dummy") # then @@ -27,8 +27,8 @@ class TestEventSeries(TestCase): def test_should_disable_itself_if_redis_not_available_1(self): # when - with patch(MODULE_PATH + ".get_redis_connection") as mock_get_redis_connection: - mock_get_redis_connection.return_value.ping.side_effect = RedisError + with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client: + mock_get_master_client.return_value.ping.side_effect = RedisError events = EventSeries("dummy") # then self.assertIsInstance(events._redis, _RedisStub) @@ -36,8 +36,8 @@ class TestEventSeries(TestCase): def test_should_disable_itself_if_redis_not_available_2(self): # when - with patch(MODULE_PATH + ".get_redis_connection") as mock_get_redis_connection: - mock_get_redis_connection.return_value.ping.return_value = False + with patch(MODULE_PATH + ".get_redis_client") as mock_get_master_client: + mock_get_master_client.return_value.ping.return_value = False events = EventSeries("dummy") # then self.assertIsInstance(events._redis, _RedisStub) diff --git a/allianceauth/services/modules/discord/discord_client/client.py b/allianceauth/services/modules/discord/discord_client/client.py index 94c2d8c0..c4923504 100644 --- a/allianceauth/services/modules/discord/discord_client/client.py +++ b/allianceauth/services/modules/discord/discord_client/client.py @@ -8,7 +8,7 @@ from uuid import uuid1 from redis import Redis import requests -from django_redis import get_redis_connection +from allianceauth.utils.cache import get_redis_client from allianceauth import __title__ as AUTH_TITLE, __url__, __version__ @@ -103,7 +103,7 @@ class DiscordClient: self._access_token = str(access_token) self._is_rate_limited = bool(is_rate_limited) if not redis: - self._redis = get_redis_connection("default") + self._redis = get_redis_client() if not isinstance(self._redis, Redis): raise RuntimeError( 'This class requires a Redis client, but none was provided ' diff --git a/allianceauth/services/modules/discord/discord_client/tests/test_client.py b/allianceauth/services/modules/discord/discord_client/tests/test_client.py index 8d2eece6..d9e16649 100644 --- a/allianceauth/services/modules/discord/discord_client/tests/test_client.py +++ b/allianceauth/services/modules/discord/discord_client/tests/test_client.py @@ -85,25 +85,18 @@ class TestBasicsAndHelpers(TestCase): client = DiscordClient(TEST_BOT_TOKEN, mock_redis, is_rate_limited=True) self.assertTrue(client.is_rate_limited) - @patch(MODULE_PATH + '.get_redis_connection') - def test_use_default_redis_if_none_provided(self, mock_caches): - my_redis = MagicMock(spec=Redis) - mock_caches.return_value = my_redis - + def test_use_default_redis_if_none_provided(self): client = DiscordClient(TEST_BOT_TOKEN) - self.assertTrue(mock_caches.called) - self.assertEqual(client._redis, my_redis) - - @patch(MODULE_PATH + '.get_redis_connection') - def test_raise_exception_if_default_cache_is_not_redis(self, mock_caches): - my_redis = MagicMock() - mock_caches.return_value = my_redis + self.assertIsInstance(client._redis, Redis) + @patch(MODULE_PATH + '.get_redis_client') + def test_raise_exception_if_redis_client_not_found(self, mock_get_redis_client): + # given + mock_get_redis_client.return_value = None + # when with self.assertRaises(RuntimeError): DiscordClient(TEST_BOT_TOKEN) - self.assertTrue(mock_caches.called) - @requests_mock.Mocker() class TestOtherMethods(TestCase): diff --git a/allianceauth/services/modules/discord/tests/piloting_tasks.py b/allianceauth/services/modules/discord/tests/piloting_tasks.py index 15ee6d0e..4b1d0c7b 100755 --- a/allianceauth/services/modules/discord/tests/piloting_tasks.py +++ b/allianceauth/services/modules/discord/tests/piloting_tasks.py @@ -35,17 +35,17 @@ import logging from uuid import uuid1 import random -from django.core.cache import caches from django.contrib.auth.models import User, Group from allianceauth.services.modules.discord.models import DiscordUser +from allianceauth.utils.cache import get_redis_client logger = logging.getLogger('allianceauth') MAX_RUNS = 3 + def clear_cache(): - default_cache = caches['default'] - redis = default_cache.get_master_client() + redis = get_redis_client() redis.flushall() logger.info('Cache flushed') diff --git a/allianceauth/services/modules/discord/tests/test_integration.py b/allianceauth/services/modules/discord/tests/test_integration.py index 9bbbd0b0..fe8fb6fb 100644 --- a/allianceauth/services/modules/discord/tests/test_integration.py +++ b/allianceauth/services/modules/discord/tests/test_integration.py @@ -14,7 +14,6 @@ from requests.exceptions import HTTPError import requests_mock from django.contrib.auth.models import Group, User -from django_redis import get_redis_connection from django.shortcuts import reverse from django.test import TransactionTestCase, TestCase from django.test.utils import override_settings @@ -23,6 +22,7 @@ from allianceauth.authentication.models import State from allianceauth.eveonline.models import EveCharacter from allianceauth.notifications.models import Notification from allianceauth.tests.auth_utils import AuthUtils +from allianceauth.utils.cache import get_redis_client from . import ( TEST_GUILD_ID, @@ -87,7 +87,7 @@ remove_guild_member_request = DiscordRequest( def clear_cache(): - redis = get_redis_connection('default') + redis = get_redis_client() redis.flushall() logger.info('Cache flushed') @@ -108,7 +108,6 @@ def reset_testdata(): class TestServiceFeatures(TransactionTestCase): fixtures = ['disable_analytics.json'] - @classmethod def setUpClass(cls): super().setUpClass() diff --git a/allianceauth/utils/__init__.py b/allianceauth/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/allianceauth/utils/cache.py b/allianceauth/utils/cache.py new file mode 100644 index 00000000..6605cd7a --- /dev/null +++ b/allianceauth/utils/cache.py @@ -0,0 +1,21 @@ +from redis import Redis + +from django.core.cache import caches + +try: + import django_redis +except ImportError: + django_redis = None + + +def get_redis_client() -> Redis: + """Get the configured redis client used by Django for caching. + + This function is a wrapper designed to work for both AA2 and AA3 + and should always be used to ensure backwards compatibility. + """ + try: + return django_redis.get_redis_connection("default") + except AttributeError: + default_cache = caches["default"] + return default_cache.get_master_client() diff --git a/allianceauth/utils/tests/__init__.py b/allianceauth/utils/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/allianceauth/utils/tests/test_cache.py b/allianceauth/utils/tests/test_cache.py new file mode 100644 index 00000000..b023eaa0 --- /dev/null +++ b/allianceauth/utils/tests/test_cache.py @@ -0,0 +1,39 @@ +from unittest.mock import MagicMock, patch + +from django.test import TestCase + +from allianceauth.utils.cache import get_redis_client + +MODULE_PATH = "allianceauth.utils.cache" + + +class RedisClientStub: + """Substitute for a Redis client.""" + + pass + + +class TestGetRedisClient(TestCase): + def test_should_work_with_aa2_api(self): + # given + mock_django_cache = MagicMock() + mock_django_cache.get_master_client.return_value = RedisClientStub() + # when + with patch(MODULE_PATH + ".django_redis", None), patch.dict( + MODULE_PATH + ".caches", {"default": mock_django_cache} + ): + client = get_redis_client() + # then + self.assertIsInstance(client, RedisClientStub) + + def test_should_work_with_aa3_api(self): + # given + mock_django_redis = MagicMock() + mock_django_redis.get_redis_connection.return_value = RedisClientStub() + # when + with patch(MODULE_PATH + ".django_redis", mock_django_redis), patch.dict( + MODULE_PATH + ".caches", {"default": None} + ): + client = get_redis_client() + # then + self.assertIsInstance(client, RedisClientStub) diff --git a/docs/conf.py b/docs/conf.py index 64700522..1a6349e5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,6 +44,8 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'recommonmark', + 'sphinxcontrib_django2', + 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/development/tech_docu/api/esi.rst b/docs/development/tech_docu/api/esi.rst index a6942e69..e9bedd8e 100644 --- a/docs/development/tech_docu/api/esi.rst +++ b/docs/development/tech_docu/api/esi.rst @@ -4,36 +4,4 @@ django-esi The django-esi package provides an interface for easy access to the ESI. -Location: ``esi`` - -This is an external package. Please also see `here `_ for it's official documentation. - -clients -=========== - -.. automodule:: esi.clients - :members: esi_client_factory - :undoc-members: - -decorators -=========== - -.. automodule:: esi.decorators - :members: - :undoc-members: - -errors -=========== - -.. automodule:: esi.errors - :members: - :undoc-members: - - -models -=========== - -.. automodule:: esi.models - :members: Scope, Token - :exclude-members: objects, provider - :undoc-members: +This is an external package. Please see `here `_ for it's documentation. diff --git a/docs/development/tech_docu/api/eveonline.rst b/docs/development/tech_docu/api/eveonline.rst index 054f7cb6..c09f76e7 100644 --- a/docs/development/tech_docu/api/eveonline.rst +++ b/docs/development/tech_docu/api/eveonline.rst @@ -11,5 +11,4 @@ models .. automodule:: allianceauth.eveonline.models :members: - :exclude-members: objects, provider - :undoc-members: + :exclude-members: DoesNotExist, MultipleObjectsReturned diff --git a/docs/development/tech_docu/api/index.md b/docs/development/tech_docu/api/index.md index be231fec..b1773beb 100644 --- a/docs/development/tech_docu/api/index.md +++ b/docs/development/tech_docu/api/index.md @@ -11,4 +11,5 @@ To reduce redundancy and help speed up development we encourage developers to ut eveonline notifications testutils + utils ``` diff --git a/docs/development/tech_docu/api/notifications.rst b/docs/development/tech_docu/api/notifications.rst index be324576..7a4fc75b 100644 --- a/docs/development/tech_docu/api/notifications.rst +++ b/docs/development/tech_docu/api/notifications.rst @@ -13,7 +13,8 @@ models =========== .. autoclass:: allianceauth.notifications.models.Notification - :members: Level, mark_viewed, set_level + :members: + :exclude-members: DoesNotExist, MultipleObjectsReturned, save managers =========== diff --git a/docs/development/tech_docu/api/utils.rst b/docs/development/tech_docu/api/utils.rst new file mode 100644 index 00000000..c27fa9fa --- /dev/null +++ b/docs/development/tech_docu/api/utils.rst @@ -0,0 +1,14 @@ +============================= +utils +============================= + +Utilities and helper functions. + +Location: ``allianceauth.utils`` + +cache +=========== + +.. automodule:: allianceauth.utils.cache + :members: + :undoc-members: diff --git a/docs/requirements.txt b/docs/requirements.txt index fc1b5a19..48692c08 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,6 +4,7 @@ sphinx_rtd_theme>=1.0.0,<2.0.0 recommonmark==0.7.1 Jinja2<3.1 docutils==0.16 +sphinxcontrib-django2 # Autodoc dependencies celery>=5.2.0,<6.0.0