Добавлен класс ConfigurationManager
This commit is contained in:
@@ -8,5 +8,6 @@
|
||||
Пакет классов менеджеров
|
||||
"""
|
||||
|
||||
from .configuration_manager import ConfigurationManager
|
||||
from .crypto_manager import CryptoManager
|
||||
from .locale_manager import LocaleManager
|
||||
|
||||
66
net/xeaf/rack/managers/configuration_manager.py
Normal file
66
net/xeaf/rack/managers/configuration_manager.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса ConfigurationManager
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
class ConfigurationManager:
|
||||
"""
|
||||
Прокси-менеджер для безопасного извлечения настроек из django.conf.settings
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_string(cls, key: str, default: str) -> str:
|
||||
"""
|
||||
Извлекает строковое значение, параметр default обязателен
|
||||
"""
|
||||
|
||||
value = getattr(settings, key, default)
|
||||
cls._assert_type(key, value, str)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def get_int(cls, key: str, default: int) -> int:
|
||||
"""
|
||||
Извлекает целочисленное значение, параметр default обязателен
|
||||
"""
|
||||
|
||||
value = getattr(settings, key, default)
|
||||
cls._assert_type(key, value, int)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def get_bool(cls, key: str, default: bool) -> bool:
|
||||
"""
|
||||
Извлекает логическое значение, параметр default обязателен.
|
||||
"""
|
||||
|
||||
value = getattr(settings, key, default)
|
||||
cls._assert_type(key, value, bool)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _assert_type(cls, key: str, value: Any, expected_type: type) -> None:
|
||||
"""
|
||||
Проверяет типы, защищая от bool/int полиморфизма
|
||||
"""
|
||||
|
||||
if expected_type is int and isinstance(value, bool):
|
||||
actual_type = bool
|
||||
else:
|
||||
actual_type = type(value)
|
||||
|
||||
if actual_type is not expected_type:
|
||||
raise ImproperlyConfigured(
|
||||
f"Configuration error: '{key}' must be of type {expected_type.__name__}, got {actual_type.__name__}."
|
||||
)
|
||||
@@ -37,7 +37,7 @@ class ExpiresAtMixin(models.Model):
|
||||
"""
|
||||
|
||||
# Проверка значений
|
||||
if seconds <=0:
|
||||
if seconds <= 0:
|
||||
raise ValueError(f"The seconds parameter must be greater than 0")
|
||||
|
||||
self.expires_at = timezone.now() + timedelta(seconds=seconds)
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
Пакет описания тестов для классов менеджеров
|
||||
"""
|
||||
|
||||
from .configuration_manager_tests import ConfigurationManagerTests
|
||||
from .crypto_manager_tests import CryptoManagerTests
|
||||
from .locale_manager_tests import LocaleManagerTests
|
||||
|
||||
116
net/xeaf/rack/tests/managers/configuration_manager_tests.py
Normal file
116
net/xeaf/rack/tests/managers/configuration_manager_tests.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса CryptoTests
|
||||
"""
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import override_settings
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from net.xeaf.rack.managers import ConfigurationManager
|
||||
|
||||
|
||||
class ConfigurationManagerTests(SimpleTestCase):
|
||||
"""
|
||||
Тесты для класса ConfigurationManager
|
||||
"""
|
||||
|
||||
def test_get_string_returns_default_if_setting_missing(self) -> None:
|
||||
"""
|
||||
Должен вернуть дефолтное значение, если настройки нет в settings.py
|
||||
"""
|
||||
|
||||
result = ConfigurationManager.get_string("NON_EXISTENT_STR_KEY", default="default_val")
|
||||
self.assertEqual(result, "default_val")
|
||||
|
||||
@override_settings(EXISTING_STR_KEY="custom_val")
|
||||
def test_get_string_returns_custom_value(self) -> None:
|
||||
"""
|
||||
Должен успешно прочитать кастомную строку из настроек проекта
|
||||
"""
|
||||
|
||||
result = ConfigurationManager.get_string("EXISTING_STR_KEY", default="default_val")
|
||||
self.assertEqual(result, "custom_val")
|
||||
|
||||
@override_settings(INVALID_STR_KEY=123)
|
||||
def test_get_string_raises_exception_on_type_mismatch(self) -> None:
|
||||
"""
|
||||
Должен взорваться исключением ImproperlyConfigured, если вместо строки пришел int
|
||||
"""
|
||||
|
||||
with self.assertRaises(ImproperlyConfigured) as context:
|
||||
ConfigurationManager.get_string("INVALID_STR_KEY", default="default_val")
|
||||
|
||||
self.assertIn("must be of type str, got int", str(context.exception))
|
||||
|
||||
def test_get_int_returns_default_if_setting_missing(self) -> None:
|
||||
"""
|
||||
Должен вернуть дефолтное число, если настройки нет в файле конфигурации
|
||||
"""
|
||||
|
||||
result = ConfigurationManager.get_int("NON_EXISTENT_INT_KEY", default=42)
|
||||
self.assertEqual(result, 42)
|
||||
|
||||
@override_settings(EXISTING_INT_KEY=100)
|
||||
def test_get_int_returns_custom_value(self) -> None:
|
||||
"""
|
||||
Должен успешно вернуть настроенный int
|
||||
"""
|
||||
|
||||
result = ConfigurationManager.get_int("EXISTING_INT_KEY", default=42)
|
||||
self.assertEqual(result, 100)
|
||||
|
||||
@override_settings(INVALID_INT_KEY="not_an_int")
|
||||
def test_get_int_raises_exception_on_type_mismatch(self) -> None:
|
||||
"""
|
||||
Должен упасть, если в числовую настройку хост-проект прокинул строку
|
||||
"""
|
||||
|
||||
with self.assertRaises(ImproperlyConfigured) as context:
|
||||
ConfigurationManager.get_int("INVALID_INT_KEY", default=42)
|
||||
|
||||
self.assertIn("must be of type int, got str", str(context.exception))
|
||||
|
||||
@override_settings(BOOLEAN_INSTEAD_OF_INT=True)
|
||||
def test_get_int_raises_exception_on_boolean_value(self) -> None:
|
||||
"""
|
||||
Хирургический тест: должен упасть, если вместо int передан bool (True/False)
|
||||
"""
|
||||
|
||||
with self.assertRaises(ImproperlyConfigured) as context:
|
||||
ConfigurationManager.get_int("BOOLEAN_INSTEAD_OF_INT", default=42)
|
||||
|
||||
self.assertIn("must be of type int, got bool", str(context.exception))
|
||||
|
||||
def test_get_bool_returns_default_if_setting_missing(self) -> None:
|
||||
"""
|
||||
Должен вернуть дефолтный булевый флаг при отсутствии настройки
|
||||
"""
|
||||
|
||||
result = ConfigurationManager.get_bool("NON_EXISTENT_BOOL_KEY", default=True)
|
||||
self.assertTrue(result)
|
||||
|
||||
@override_settings(EXISTING_BOOL_KEY=False)
|
||||
def test_get_bool_returns_custom_value(self) -> None:
|
||||
"""
|
||||
Должен успешно прочитать переопределенный булевый флаг
|
||||
"""
|
||||
|
||||
result = ConfigurationManager.get_bool("EXISTING_BOOL_KEY", default=True)
|
||||
self.assertFalse(result)
|
||||
|
||||
@override_settings(INT_INSTEAD_OF_BOOL=1)
|
||||
def test_get_bool_raises_exception_on_int_value(self) -> None:
|
||||
"""
|
||||
Должен выбросить исключение, если вместо честного bool передан int
|
||||
"""
|
||||
|
||||
with self.assertRaises(ImproperlyConfigured) as context:
|
||||
ConfigurationManager.get_bool("INT_INSTEAD_OF_BOOL", default=True)
|
||||
|
||||
self.assertIn("must be of type bool, got int", str(context.exception))
|
||||
Reference in New Issue
Block a user