refactor: implement generic optional import mechanisms

this project is actively using optional imports, which leads to
duplicate handling here and there. This commit implements logic in
special method to reduce complexity and simplify tests paths
This commit is contained in:
2026-07-22 16:33:59 +03:00
parent bd26e8da68
commit f37c5bd2a2
16 changed files with 83 additions and 83 deletions
@@ -21,6 +21,8 @@
from logging import NullHandler
from typing import Any
from ahriman.core.module_loader import optional_module
__all__ = ["JournalHandler"]
@@ -40,7 +42,5 @@ class _JournalHandler(NullHandler):
del args, kwargs
try:
from systemd.journal import JournalHandler # type: ignore[import-untyped]
except ImportError:
JournalHandler = _JournalHandler
systemd_journal = optional_module("systemd.journal")
JournalHandler = systemd_journal.JournalHandler if systemd_journal else _JournalHandler
@@ -26,6 +26,7 @@ from typing import ClassVar, Literal
from ahriman.core.configuration import Configuration
from ahriman.core.log.http_log_handler import HttpLogHandler
from ahriman.core.log.log_context import LogContext
from ahriman.core.module_loader import optional_module
from ahriman.models.log_handler import LogHandler
from ahriman.models.repository_id import RepositoryId
@@ -65,14 +66,11 @@ class LogLoader:
if selected is not None:
return selected
try:
from systemd.journal import JournalHandler # type: ignore[import-untyped]
del JournalHandler
if optional_module("systemd.journal"):
return LogHandler.Journald # journald import was found
except ImportError:
if LogLoader.DEFAULT_SYSLOG_DEVICE.exists():
return LogHandler.Syslog
return LogHandler.Console
if LogLoader.DEFAULT_SYSLOG_DEVICE.exists():
return LogHandler.Syslog
return LogHandler.Console
@staticmethod
def load(repository_id: RepositoryId, configuration: Configuration, handler: LogHandler, *,
+17 -2
View File
@@ -26,8 +26,7 @@ from pkgutil import ModuleInfo, walk_packages
from types import ModuleType
from typing import Any, TypeGuard, TypeVar
__all__ = ["implementations"]
__all__ = ["implementations", "optional_module"]
T = TypeVar("T")
@@ -74,3 +73,19 @@ def implementations(root_module: ModuleType, base_class: type[T]) -> Iterator[ty
for _, attribute in inspect.getmembers(module, is_base_class):
yield attribute
def optional_module(module_name: str) -> ModuleType | None:
"""
import an optional module
Args:
module_name(str): fully qualified module name
Returns:
ModuleType | None: imported module or ``None`` when it cannot be imported
"""
try:
return import_module(module_name)
except ImportError:
return None
@@ -2,7 +2,7 @@ import ahriman.web.views
from pathlib import Path
from ahriman.core.module_loader import _modules, implementations
from ahriman.core.module_loader import _modules, implementations, optional_module
from ahriman.web.views.base import BaseView
@@ -23,3 +23,17 @@ def test_implementations() -> None:
assert routes
assert all(isinstance(view, type) for view in routes)
assert all(issubclass(view, BaseView) for view in routes)
def test_optional_module() -> None:
"""
must import an available module
"""
assert optional_module("ahriman.web.views") is ahriman.web.views
def test_optional_module_fallback() -> None:
"""
must return none when the module cannot be imported
"""
assert optional_module("missing_ahriman_module") is None