refactor: move repository and service imports to module level in dependencies.py
Move all repository imports (session_repo, blocklist_repo, import_log_repo, settings_repo, history_archive_repo, geo_cache_repo, fail2ban_db_repo) and service imports (auth_service, health_service, default_fail2ban_metadata_service) to module level in app/dependencies.py. This eliminates the pattern of local imports inside provider functions, providing consistency and reducing import overhead. The from app.db import open_db remains a local import since it's only used within get_db(). - Verified no circular dependencies exist - All repository and service provider functions simplified to return modules - Updated Architekture.md § 2.3 to document the module-level import pattern - All tests pass (28 dependency + auth tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -599,6 +599,28 @@ Every injectable dependency follows this structure:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Module-Level Imports:**
|
||||||
|
|
||||||
|
All repository and service modules are imported at module level in `app/dependencies.py`. These imports are safe at the top because no circular dependencies exist — repositories and services do not import from `dependencies.py`. This follows the principle of importing dependencies early and consistently:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# app/dependencies.py (top of file)
|
||||||
|
from app.repositories import (
|
||||||
|
blocklist_repo,
|
||||||
|
fail2ban_db_repo,
|
||||||
|
session_repo,
|
||||||
|
# ... other repository modules
|
||||||
|
)
|
||||||
|
from app.services import auth_service, health_service
|
||||||
|
from app.services.fail2ban_metadata_service import default_fail2ban_metadata_service
|
||||||
|
|
||||||
|
# Provider functions simply return the module
|
||||||
|
async def get_session_repo() -> SessionRepository:
|
||||||
|
return session_repo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exception**: The `from app.db import open_db` import remains local to `get_db()` because it is only used within that specific function and the module load overhead is avoided.
|
||||||
|
|
||||||
#### Service Composition Root
|
#### Service Composition Root
|
||||||
|
|
||||||
Services are **not instantiated by a container**. Instead, they are **composed by routers and tasks through explicit parameter passing**. This keeps dependencies visible and avoids implicit side effects.
|
Services are **not instantiated by a container**. Instead, they are **composed by routers and tasks through explicit parameter passing**. This keeps dependencies visible and avoids implicit side effects.
|
||||||
|
|||||||
@@ -60,6 +60,20 @@ from app.utils.rate_limiter import RateLimiter
|
|||||||
from app.utils.runtime_state import ApplicationState, JailServiceState, RuntimeState
|
from app.utils.runtime_state import ApplicationState, JailServiceState, RuntimeState
|
||||||
from app.utils.session_cache import NoOpSessionCache, SessionCache
|
from app.utils.session_cache import NoOpSessionCache, SessionCache
|
||||||
|
|
||||||
|
# Module-level imports for repositories and services
|
||||||
|
# These are safe at module level since no circular dependencies exist
|
||||||
|
from app.repositories import (
|
||||||
|
blocklist_repo,
|
||||||
|
fail2ban_db_repo,
|
||||||
|
geo_cache_repo,
|
||||||
|
history_archive_repo,
|
||||||
|
import_log_repo,
|
||||||
|
session_repo,
|
||||||
|
settings_repo,
|
||||||
|
)
|
||||||
|
from app.services import auth_service, health_service
|
||||||
|
from app.services.fail2ban_metadata_service import default_fail2ban_metadata_service
|
||||||
|
|
||||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||||
|
|
||||||
|
|
||||||
@@ -255,8 +269,6 @@ async def get_session_repo() -> SessionRepository:
|
|||||||
Protocol interface — its top-level async functions must match the Protocol
|
Protocol interface — its top-level async functions must match the Protocol
|
||||||
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
||||||
"""
|
"""
|
||||||
from app.repositories import session_repo # noqa: PLC0415
|
|
||||||
|
|
||||||
return session_repo
|
return session_repo
|
||||||
|
|
||||||
|
|
||||||
@@ -267,8 +279,6 @@ async def get_blocklist_repo() -> BlocklistRepository:
|
|||||||
Protocol interface — its top-level async functions must match the Protocol
|
Protocol interface — its top-level async functions must match the Protocol
|
||||||
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
||||||
"""
|
"""
|
||||||
from app.repositories import blocklist_repo # noqa: PLC0415
|
|
||||||
|
|
||||||
return cast("BlocklistRepository", blocklist_repo)
|
return cast("BlocklistRepository", blocklist_repo)
|
||||||
|
|
||||||
|
|
||||||
@@ -279,8 +289,6 @@ async def get_import_log_repo() -> ImportLogRepository:
|
|||||||
Protocol interface — its top-level async functions must match the Protocol
|
Protocol interface — its top-level async functions must match the Protocol
|
||||||
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
||||||
"""
|
"""
|
||||||
from app.repositories import import_log_repo # noqa: PLC0415
|
|
||||||
|
|
||||||
return cast("ImportLogRepository", import_log_repo)
|
return cast("ImportLogRepository", import_log_repo)
|
||||||
|
|
||||||
|
|
||||||
@@ -291,8 +299,6 @@ async def get_settings_repo() -> SettingsRepository:
|
|||||||
Protocol interface — its top-level async functions must match the Protocol
|
Protocol interface — its top-level async functions must match the Protocol
|
||||||
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
||||||
"""
|
"""
|
||||||
from app.repositories import settings_repo # noqa: PLC0415
|
|
||||||
|
|
||||||
return cast("SettingsRepository", settings_repo)
|
return cast("SettingsRepository", settings_repo)
|
||||||
|
|
||||||
|
|
||||||
@@ -304,8 +310,6 @@ async def get_history_archive_repo() -> HistoryArchiveRepository:
|
|||||||
must match the Protocol signatures exactly. This is documented in
|
must match the Protocol signatures exactly. This is documented in
|
||||||
Backend-Development.md § 13.7.1.
|
Backend-Development.md § 13.7.1.
|
||||||
"""
|
"""
|
||||||
from app.repositories import history_archive_repo # noqa: PLC0415
|
|
||||||
|
|
||||||
return cast("HistoryArchiveRepository", history_archive_repo)
|
return cast("HistoryArchiveRepository", history_archive_repo)
|
||||||
|
|
||||||
|
|
||||||
@@ -316,8 +320,6 @@ async def get_geo_cache_repo() -> GeoCacheRepository:
|
|||||||
Protocol interface — its top-level async functions must match the Protocol
|
Protocol interface — its top-level async functions must match the Protocol
|
||||||
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
signatures exactly. This is documented in Backend-Development.md § 13.7.1.
|
||||||
"""
|
"""
|
||||||
from app.repositories import geo_cache_repo # noqa: PLC0415
|
|
||||||
|
|
||||||
return cast("GeoCacheRepository", geo_cache_repo)
|
return cast("GeoCacheRepository", geo_cache_repo)
|
||||||
|
|
||||||
|
|
||||||
@@ -329,8 +331,6 @@ async def get_fail2ban_db_repo() -> Fail2BanDbRepository:
|
|||||||
match the Protocol signatures exactly. This is documented in
|
match the Protocol signatures exactly. This is documented in
|
||||||
Backend-Development.md § 13.7.1.
|
Backend-Development.md § 13.7.1.
|
||||||
"""
|
"""
|
||||||
from app.repositories import fail2ban_db_repo # noqa: PLC0415
|
|
||||||
|
|
||||||
return cast("Fail2BanDbRepository", fail2ban_db_repo)
|
return cast("Fail2BanDbRepository", fail2ban_db_repo)
|
||||||
|
|
||||||
|
|
||||||
@@ -375,8 +375,6 @@ async def get_health_probe() -> Callable[[str], Awaitable[ServerStatus]]:
|
|||||||
A callable that probes the fail2ban socket and returns ServerStatus.
|
A callable that probes the fail2ban socket and returns ServerStatus.
|
||||||
This allows explicit dependency injection to avoid hidden service coupling.
|
This allows explicit dependency injection to avoid hidden service coupling.
|
||||||
"""
|
"""
|
||||||
from app.services import health_service # noqa: PLC0415
|
|
||||||
|
|
||||||
return health_service.probe
|
return health_service.probe
|
||||||
|
|
||||||
|
|
||||||
@@ -387,8 +385,6 @@ async def get_fail2ban_metadata_service() -> object:
|
|||||||
The singleton Fail2BanMetadataService for resolving fail2ban metadata
|
The singleton Fail2BanMetadataService for resolving fail2ban metadata
|
||||||
(such as the database path) and caching results.
|
(such as the database path) and caching results.
|
||||||
"""
|
"""
|
||||||
from app.services.fail2ban_metadata_service import default_fail2ban_metadata_service # noqa: PLC0415
|
|
||||||
|
|
||||||
return default_fail2ban_metadata_service
|
return default_fail2ban_metadata_service
|
||||||
|
|
||||||
|
|
||||||
@@ -612,8 +608,6 @@ async def require_auth(
|
|||||||
if cached is not None:
|
if cached is not None:
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
from app.services import auth_service # noqa: PLC0415
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
session = await auth_service.validate_session(
|
session = await auth_service.validate_session(
|
||||||
db,
|
db,
|
||||||
|
|||||||
Reference in New Issue
Block a user