Refactor: Make model packages true leaf nodes - remove app-layer dependencies
Models in app/models/ are now pure data classes with no cross-layer dependencies. This ensures the models layer remains a true leaf node in the dependency graph. Changes: - Create app/models/_common.py with shared types (TimeRange, bucket_count, constants) - Move TimeRange and time-range constants from ban.py to _common.py - Update history.py, routers, and services to import from _common.py - Remove imports from app.config and app.utils from config.py models - Move field validators from models to router layer: - Add log_target validation in config_misc router - Add log_path validation in jail_config router - Update test_models.py to reflect validators moved to router layer - Update documentation (Architekture.md, Backend-Development.md) with model layering rules - Fix import ordering and type annotations in affected files Model layering rule: Models may only import from: ✓ Standard library and third-party packages (Pydantic, typing) ✓ Other models in app/models/ (sibling models) ✓ app.models.response (response envelopes) ✗ app.services, app.config, app.utils, or any application layer Validation requiring app-level state (settings, allowed directories) now happens at the router or service layer, not in model validators. Fixes: Models were not true leaf nodes due to circular imports and app-layer dependencies Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -19,15 +19,17 @@ import aiohttp
|
||||
import structlog
|
||||
|
||||
from app.exceptions import JailNotFoundError, JailOperationError
|
||||
from app.models.ban import (
|
||||
BLOCKLIST_JAIL,
|
||||
from app.models._common import (
|
||||
BUCKET_SECONDS,
|
||||
BUCKET_SIZE_LABEL,
|
||||
BanOrigin,
|
||||
TimeRange,
|
||||
_derive_origin,
|
||||
bucket_count,
|
||||
)
|
||||
from app.models.ban import (
|
||||
BLOCKLIST_JAIL,
|
||||
BanOrigin,
|
||||
_derive_origin,
|
||||
)
|
||||
from app.models.ban_domain import (
|
||||
DomainActiveBan,
|
||||
DomainActiveBanList,
|
||||
@@ -320,7 +322,7 @@ async def get_active_bans(
|
||||
except (TimeoutError, aiohttp.ClientError, OSError):
|
||||
log.warning("active_bans_batch_geo_failed")
|
||||
geo_map = {}
|
||||
enriched: list[ActiveBan] = []
|
||||
enriched: list[DomainActiveBan] = []
|
||||
for ban in bans:
|
||||
geo = geo_map.get(ban.ip)
|
||||
if geo is not None:
|
||||
|
||||
@@ -15,12 +15,12 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import structlog
|
||||
|
||||
from app.models.ban import BanOrigin, TimeRange
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
import aiosqlite
|
||||
|
||||
from app.models._common import TimeRange
|
||||
from app.models.ban import BanOrigin
|
||||
from app.models.geo import GeoEnricher, GeoInfo
|
||||
from app.repositories.protocols import HistoryArchiveRepository
|
||||
from app.services.protocols import Fail2BanMetadataService
|
||||
|
||||
@@ -14,8 +14,9 @@ if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
import aiosqlite
|
||||
|
||||
from app.models._common import TimeRange
|
||||
from app.models.auth import Session
|
||||
from app.models.ban import BanOrigin, JailBannedIpsResponse, TimeRange
|
||||
from app.models.ban import BanOrigin, JailBannedIpsResponse
|
||||
from app.models.blocklist import (
|
||||
BlocklistSource,
|
||||
ImportLogListResponse,
|
||||
|
||||
Reference in New Issue
Block a user