refactor: reorganize models into subpackages

Move models into domain subpackages (max 4 files each):
- models/tool/ — tool_type, tool_instance, tool_definition_manifest
- models/config/ — config_profile
- models/user/ — user, user_config, ssh_key
- models/project/ — project, git_repository, workspace
- models/system/ — health_check, notification, instance_event, terminal_session

models/__init__.py continues to re-export all symbols, so consumers
using 'from src.models import X' are unaffected. Updated direct file
imports across the backend to use the new paths.

Quality gates: py_compile passed, ruff passed.
This commit is contained in:
2026-06-04 10:00:10 +02:00
parent 0127d283a6
commit 0591b00ded
62 changed files with 136 additions and 87 deletions
+1
View File
@@ -0,0 +1 @@
"""Config module."""
+4 -4
View File
@@ -14,10 +14,10 @@ from sqlalchemy.orm import selectinload
from src.api.shared_validators import validate_env_vars as _validate_env_vars
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.config_profile import ConfigProfile, ConfigProfileInclude
from src.models import ConfigProfile, ConfigProfileInclude
from src.models.project import Project
from src.models.tool_type import ToolType
from src.models.user_config import UserConfig
from src.models import ToolType
from src.models import UserConfig
from src.services.config_profile_resolver import (
ConfigProfileCycleError,
check_include_cycle,
@@ -991,7 +991,7 @@ async def validate_git_url(
env = None
key_path = None
if data.ssh_key_id:
from src.models.ssh_key import SSHKey
from src.models import SSHKey
from src.services.ssh_keys import _get_fernet
try:
+2 -2
View File
@@ -5,9 +5,9 @@ from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.git_repository import GitRepository
from src.models import GitRepository
from src.models.project import Project
from src.models.ssh_key import SSHKey
from src.models import SSHKey
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
+2 -2
View File
@@ -17,8 +17,8 @@ from src.auth.dependencies import (
get_db_session,
)
from src.config import Settings
from src.models.git_repository import GitRepository
from src.models.ssh_key import SSHKey
from src.models import GitRepository
from src.models import SSHKey
from src.utils.git_files import (
commit_file,
get_file_content,
+2 -2
View File
@@ -8,8 +8,8 @@ from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.tool_instance import ToolInstance
from src.models.tool_type import ToolType
from src.models import ToolInstance
from src.models import ToolType
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -9,7 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user, get_db_session
from src.models.user import User
from src.models.user_config import UserConfig
from src.models import UserConfig
from src.services.notification_service import notification_service
router = APIRouter(prefix="/notifications", tags=["notifications"])
+1
View File
@@ -0,0 +1 @@
"""Project module."""
+4 -4
View File
@@ -13,10 +13,10 @@ from src.auth.dependencies import (
get_current_user_id,
get_db_session,
)
from src.models.git_repository import GitRepository
from src.models import GitRepository
from src.models.project import Project
from src.models.ssh_key import SSHKey
from src.models.tool_instance import ToolInstance
from src.models import SSHKey
from src.models import ToolInstance
router = APIRouter(prefix="/projects", tags=["projects"])
@@ -101,7 +101,7 @@ async def list_projects(
)
projects = result.scalars().all()
from src.models.workspace import Workspace
from src.models import Workspace
enriched = []
for project in projects:
+1 -1
View File
@@ -12,7 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import _get_user, get_current_user_id, get_db_session
from src.config import Settings
from src.models.ssh_key import SSHKey
from src.models import SSHKey
router = APIRouter(prefix="/ssh-keys", tags=["ssh-keys"])
+1
View File
@@ -0,0 +1 @@
"""System module."""
+3 -3
View File
@@ -12,9 +12,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
from starlette.websockets import WebSocketDisconnect
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.terminal_session import TerminalSessionModel
from src.models.tool_instance import ToolInstance
from src.models.tool_type import ToolType
from src.models import TerminalSessionModel
from src.models import ToolInstance
from src.models import ToolType
from src.services.terminal_manager import MaxSessionsExceededError, terminal_manager
router = APIRouter()
+1
View File
@@ -0,0 +1 @@
"""Tool module."""
+2 -2
View File
@@ -9,8 +9,8 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.tool_definition_manifest import ToolDefinitionManifest
from src.models.tool_type import ToolType
from src.models import ToolDefinitionManifest
from src.models import ToolType
from src.services.manifest_compiler import (
compile_compose,
compile_dockerfile,
+11 -11
View File
@@ -30,12 +30,12 @@ from src.auth.dependencies import (
)
from src.services.event_bus import InstanceEventBus
from src.services.lifecycle_hooks import publish_lifecycle_event
from src.models.config_profile import ConfigProfile
from src.models.git_repository import GitRepository
from src.models import ConfigProfile
from src.models import GitRepository
from src.models.project import Project
from src.models.ssh_key import SSHKey
from src.models.tool_instance import ToolInstance
from src.models.tool_type import ToolType
from src.models import SSHKey
from src.models import ToolInstance
from src.models import ToolType
from src.services.clone import check_dirty_state, clone_repository
from src.services.config_profile_resolver import (
ConfigProfileCycleError,
@@ -856,7 +856,7 @@ async def create_instance(
workspace = None
workspace_id = None
if data.workspace_id:
from src.models.workspace import Workspace as WorkspaceModel
from src.models import Workspace as WorkspaceModel
try:
workspace_id = uuid.UUID(data.workspace_id)
@@ -1056,7 +1056,7 @@ services:
elif tool_type.definition_type == "manifest":
# Manifest-based: generate compose only; image built lazily on start
from src.models.tool_definition_manifest import ToolDefinitionManifest
from src.models import ToolDefinitionManifest
manifest_def = await session.get(
ToolDefinitionManifest, tool_type.manifest_id
@@ -1341,7 +1341,7 @@ async def _prepare_manifest_instance(
Returns:
Tuple of (image_tag, compose_content, resolved_manifest, home_dir)
"""
from src.models.tool_definition_manifest import ToolDefinitionManifest
from src.models import ToolDefinitionManifest
tool_type = await session.get(ToolType, instance.tool_type_id)
manifest_def = await session.get(ToolDefinitionManifest, tool_type.manifest_id)
@@ -1537,7 +1537,7 @@ async def start_instance(
container_uid = 0
container_gid = 0
if tool_type and tool_type.definition_type == "manifest" and tool_type.manifest_id:
from src.models.tool_definition_manifest import ToolDefinitionManifest
from src.models import ToolDefinitionManifest
manifest_def = await session.get(ToolDefinitionManifest, tool_type.manifest_id)
if manifest_def:
@@ -1724,7 +1724,7 @@ async def start_instance(
# Determine repo path (workspace takes precedence)
repo_path = ""
if instance.workspace_id:
from src.models.workspace import Workspace as WorkspaceModel
from src.models import Workspace as WorkspaceModel
workspace = await session.get(WorkspaceModel, instance.workspace_id)
if workspace:
@@ -2767,7 +2767,7 @@ async def get_instance_events(
List of event dictionaries.
"""
from sqlalchemy import select
from src.models.instance_event import InstanceEvent
from src.models import InstanceEvent
_user = await _get_user(session, user_id)
_project = await _get_owned_project(project_id, user_id, session)
+1 -1
View File
@@ -12,7 +12,7 @@ from src.api.tool_types_validation import (
validate_required_variables,
)
from src.auth.dependencies import _get_user, get_current_user_id, get_db_session
from src.models.tool_type import ToolType
from src.models import ToolType
from src.models.user import User
router = APIRouter(prefix="/tool-types", tags=["tool-types"])
+1
View File
@@ -0,0 +1 @@
"""User module."""
+1 -1
View File
@@ -7,7 +7,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import _get_user, get_current_user_id, get_db_session
from src.models.user_config import UserConfig
from src.models import UserConfig
logger = logging.getLogger(__name__)
+1
View File
@@ -0,0 +1 @@
"""Workspace module."""
+1 -1
View File
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.workspace import Workspace
from src.models import Workspace
from src.services.file_service import FileService
router = APIRouter(prefix="/workspaces/{workspace_id}/files")
+1 -1
View File
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.workspace import Workspace
from src.models import Workspace
from src.services.git_operations import GitOperations
router = APIRouter(prefix="/workspaces/{workspace_id}/git")
+2 -2
View File
@@ -7,8 +7,8 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.tool_instance import ToolInstance
from src.models.workspace import Workspace
from src.models import ToolInstance
from src.models import Workspace
router = APIRouter(prefix="/workspaces/{workspace_id}/instances")
+3 -3
View File
@@ -9,9 +9,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from src.auth.dependencies import get_current_user_id, get_db_session
from src.models.git_repository import GitRepository
from src.models.tool_instance import ToolInstance
from src.models.workspace import Workspace
from src.models import GitRepository
from src.models import ToolInstance
from src.models import Workspace
from src.services.workspace_manager import WorkspaceHasInstancesError, WorkspaceManager
logger = logging.getLogger(__name__)
+2 -2
View File
@@ -29,8 +29,8 @@ from src.api.workspace_git import router as workspace_git_router
from src.api.workspace_instances import router as workspace_instances_router
from src.api.workspaces import all_workspaces_router, router as workspaces_router
from src.config import Settings
from src.models.notification import Notification # noqa: F401 Alembic model discovery
from src.models.terminal_session import TerminalSessionModel # noqa: F401 Alembic model discovery
from src.models import Notification # noqa: F401 Alembic model discovery
from src.models import TerminalSessionModel # noqa: F401 Alembic model discovery
from src.database import init_database
from src.logging_config import (
ExceptionLoggingMiddleware,
+14 -14
View File
@@ -1,18 +1,18 @@
from src.models.base import Base
from src.models.config_profile import ConfigProfile, ConfigProfileInclude
from src.models.git_repository import GitRepository
from src.models.health_check import HealthCheck
from src.models.instance_event import InstanceEvent
from src.models.notification import Notification
from src.models.project import Project
from src.models.ssh_key import SSHKey
from src.models.terminal_session import TerminalSessionModel
from src.models.tool_definition_manifest import ToolDefinitionManifest
from src.models.tool_instance import ToolInstance
from src.models.tool_type import ToolType
from src.models.user import User
from src.models.user_config import UserConfig
from src.models.workspace import Workspace
from src.models.config.config_profile import ConfigProfile, ConfigProfileInclude
from src.models.project.git_repository import GitRepository
from src.models.project.project import Project
from src.models.project.workspace import Workspace
from src.models.system.health_check import HealthCheck
from src.models.system.instance_event import InstanceEvent
from src.models.system.notification import Notification
from src.models.system.terminal_session import TerminalSessionModel
from src.models.tool.tool_definition_manifest import ToolDefinitionManifest
from src.models.tool.tool_instance import ToolInstance
from src.models.tool.tool_type import ToolType
from src.models.user.ssh_key import SSHKey
from src.models.user.user import User
from src.models.user.user_config import UserConfig
__all__ = [
"Base",
+5
View File
@@ -0,0 +1,5 @@
"""Config models module."""
from src.models.config.config_profile import ConfigProfile, ConfigProfileInclude
__all__ = ["ConfigProfile", "ConfigProfileInclude"]
@@ -17,7 +17,7 @@ from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
if TYPE_CHECKING:
from src.models.project import Project
from src.models.tool_type import ToolType
from src.models import ToolType
from src.models.user import User
+7
View File
@@ -0,0 +1,7 @@
"""Project models module."""
from src.models.project.git_repository import GitRepository
from src.models.project.project import Project
from src.models.project.workspace import Workspace
__all__ = ["GitRepository", "Project", "Workspace"]
@@ -10,7 +10,7 @@ from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
if TYPE_CHECKING:
from src.models.project import Project
from src.models.ssh_key import SSHKey
from src.models import SSHKey
from src.models.user import User
@@ -8,8 +8,8 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
if TYPE_CHECKING:
from src.models.git_repository import GitRepository
from src.models.ssh_key import SSHKey
from src.models import GitRepository
from src.models import SSHKey
from src.models.user import User
@@ -12,7 +12,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.base import Base, TimestampMixin
if TYPE_CHECKING:
from src.models.git_repository import GitRepository
from src.models import GitRepository
from src.models.user import User
+8
View File
@@ -0,0 +1,8 @@
"""System models module."""
from src.models.system.health_check import HealthCheck
from src.models.system.instance_event import InstanceEvent
from src.models.system.notification import Notification
from src.models.system.terminal_session import TerminalSessionModel
__all__ = ["HealthCheck", "InstanceEvent", "Notification", "TerminalSessionModel"]
+7
View File
@@ -0,0 +1,7 @@
"""Tool models module."""
from src.models.tool.tool_definition_manifest import ToolDefinitionManifest
from src.models.tool.tool_instance import ToolInstance
from src.models.tool.tool_type import ToolType
__all__ = ["ToolDefinitionManifest", "ToolInstance", "ToolType"]
@@ -9,12 +9,12 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
if TYPE_CHECKING:
from src.models.config_profile import ConfigProfile
from src.models.git_repository import GitRepository
from src.models import ConfigProfile
from src.models import GitRepository
from src.models.project import Project
from src.models.tool_type import ToolType
from src.models import ToolType
from src.models.user import User
from src.models.workspace import Workspace
from src.models import Workspace
class ToolInstance(UUIDPrimaryKeyMixin, TimestampMixin, Base):
@@ -6,8 +6,7 @@ from sqlalchemy import Uuid as UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
from src.models.tool_definition_manifest import ToolDefinitionManifest
from src.models.tool.tool_definition_manifest import ToolDefinitionManifest
if TYPE_CHECKING:
from src.models.user import User
+7
View File
@@ -0,0 +1,7 @@
"""User models module."""
from src.models.user.ssh_key import SSHKey
from src.models.user.user import User
from src.models.user.user_config import UserConfig
__all__ = ["SSHKey", "User", "UserConfig"]
@@ -7,8 +7,8 @@ from src.models.base import Base, TimestampMixin, UUIDPrimaryKeyMixin
if TYPE_CHECKING:
from src.models.project import Project
from src.models.ssh_key import SSHKey
from src.models.user_config import UserConfig
from src.models import SSHKey
from src.models import UserConfig
class User(UUIDPrimaryKeyMixin, TimestampMixin, Base):
+1
View File
@@ -0,0 +1 @@
"""Config module."""
+1
View File
@@ -0,0 +1 @@
"""Project module."""
+1
View File
@@ -0,0 +1 @@
"""System module."""
+1
View File
@@ -0,0 +1 @@
"""Tool module."""
+1
View File
@@ -0,0 +1 @@
"""User module."""
+1 -1
View File
@@ -5,7 +5,7 @@ import logging
from sqlalchemy import select, text
from src.database import SessionLocal
from src.models.tool_type import ToolType
from src.models import ToolType
logger = logging.getLogger(__name__)
+1
View File
@@ -0,0 +1 @@
"""Config module."""
@@ -13,7 +13,7 @@ from typing import Any
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.models.config_profile import ConfigProfile, ConfigProfileInclude
from src.models import ConfigProfile, ConfigProfileInclude
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -4,7 +4,7 @@ import logging
import os
from dataclasses import dataclass
from src.models.workspace import Workspace
from src.models import Workspace
logger = logging.getLogger(__name__)
+1
View File
@@ -0,0 +1 @@
"""Git module."""
+1 -1
View File
@@ -4,7 +4,7 @@ import asyncio
import logging
from dataclasses import dataclass
from src.models.workspace import Workspace
from src.models import Workspace
logger = logging.getLogger(__name__)
+2 -2
View File
@@ -10,8 +10,8 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.database import SessionLocal
from src.models.health_check import HealthCheck
from src.models.tool_instance import ToolInstance
from src.models import HealthCheck
from src.models import ToolInstance
from src.services.correlation import get_correlation_id
from src.services.docker import get_container_status
from src.services.tunnel import check_tunnel_health
@@ -0,0 +1 @@
"""Instance module."""
+2 -2
View File
@@ -6,8 +6,8 @@ from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from src.models.instance_event import InstanceEvent
from src.models.tool_instance import ToolInstance
from src.models import InstanceEvent
from src.models import ToolInstance
from src.services.correlation import get_correlation_id
from src.services.event_bus import InstanceEventBus, InstanceEventPayload
from src.services.notification_service import notification_service
@@ -8,7 +8,7 @@ from sqlalchemy import func, select, update
from sqlalchemy.engine import CursorResult
from sqlalchemy.ext.asyncio import AsyncSession
from src.models.notification import Notification
from src.models import Notification
class NotificationService:
+1
View File
@@ -0,0 +1 @@
"""Shared module."""
@@ -0,0 +1 @@
"""Terminal module."""
+1 -1
View File
@@ -9,7 +9,7 @@ from fastapi import WebSocket
from sqlalchemy.dialects.postgresql import insert as pg_insert
from src.database import SessionLocal
from src.models.terminal_session import TerminalSessionModel
from src.models import TerminalSessionModel
from src.services.terminal_session import TerminalSession
logger = logging.getLogger(__name__)
+7 -7
View File
@@ -14,15 +14,15 @@ from typing import TYPE_CHECKING
from sqlalchemy import select
from src.models.workspace import Workspace
from src.models import Workspace
from src.services.git_service import GitService
from src.services.ssh_keys import _get_fernet
if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession
from src.models.git_repository import GitRepository
from src.models.tool_instance import ToolInstance
from src.models import GitRepository
from src.models import ToolInstance
logger = logging.getLogger(__name__)
@@ -96,7 +96,7 @@ class WorkspaceManager:
# Load SSH key if repo has one
ssh_key = None
if getattr(repo, "ssh_key_id", None) and session is not None:
from src.models.ssh_key import SSHKey
from src.models import SSHKey
result = await session.execute(
select(SSHKey).where(SSHKey.id == repo.ssh_key_id)
@@ -181,8 +181,8 @@ class WorkspaceManager:
# Load SSH key if repo has one
ssh_key = None
if session is not None:
from src.models.git_repository import GitRepository
from src.models.ssh_key import SSHKey
from src.models import GitRepository
from src.models import SSHKey
repo = await session.get(GitRepository, workspace.repo_id)
if repo and getattr(repo, "ssh_key_id", None):
@@ -241,7 +241,7 @@ class WorkspaceManager:
session: AsyncSession,
) -> list[ToolInstance]:
"""Get all tool instances associated with this workspace."""
from src.models.tool_instance import ToolInstance
from src.models import ToolInstance
result = await session.execute(
select(ToolInstance).where(ToolInstance.workspace_id == workspace.id)