refactor: imports and reorganize domain structure

- Updated import statements across multiple modules to use relative imports for better encapsulation.
- Consolidated and organized the `__init__.py` files in various domain packages to expose necessary classes and functions.
- Improved code readability and maintainability by grouping related imports and removing unused ones.
- Ensured consistent import patterns across the domain, enhancing the overall structure of the codebase.
This commit is contained in:
shiyu
2026-01-09 17:28:10 +08:00
parent a727e77341
commit 6b2ada0b42
69 changed files with 420 additions and 169 deletions

View File

@@ -15,7 +15,7 @@ from domain.agent import api as agent
from domain.virtual_fs import api as virtual_fs
from domain.virtual_fs.mapping import s3_api, webdav_api
from domain.virtual_fs.search import search_api
from domain.audit import router as audit
from domain.audit import api as audit
def include_routers(app: FastAPI):
@@ -37,4 +37,4 @@ def include_routers(app: FastAPI):
app.include_router(s3_api.router)
app.include_router(offline_downloads.router)
app.include_router(email.router)
app.include_router(audit)
app.include_router(audit.router)

View File

@@ -1,6 +1,6 @@
from tortoise import Tortoise
from domain.adapters.registry import runtime_registry
from domain.adapters import runtime_registry
TORTOISE_ORM = {
"connections": {"default": "sqlite://data/db/db.sqlite3"},

7
domain/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
"""
domain业务域层
约定:跨包只从各子包 `__init__.py` 导入公开 API。
"""
__all__: list[str] = []

View File

@@ -1 +1,24 @@
from .providers import BaseAdapter
from .registry import (
RuntimeRegistry,
discover_adapters,
get_config_schema,
get_config_schemas,
normalize_adapter_type,
runtime_registry,
)
from .service import AdapterService
from .types import AdapterCreate, AdapterOut
__all__ = [
"BaseAdapter",
"RuntimeRegistry",
"discover_adapters",
"get_config_schema",
"get_config_schemas",
"normalize_adapter_type",
"runtime_registry",
"AdapterService",
"AdapterCreate",
"AdapterOut",
]

View File

@@ -4,10 +4,9 @@ from fastapi import APIRouter, Depends, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.adapters.service import AdapterService
from domain.adapters.types import AdapterCreate
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.auth import User, get_current_active_user
from .service import AdapterService
from .types import AdapterCreate
router = APIRouter(prefix="/api/adapters", tags=["adapters"])

View File

@@ -4,7 +4,7 @@ from importlib import import_module
from typing import Callable, Dict
from models import StorageAdapter
from domain.adapters.providers.base import BaseAdapter
from .providers.base import BaseAdapter
AdapterFactory = Callable[[StorageAdapter], BaseAdapter]
@@ -21,7 +21,7 @@ def normalize_adapter_type(value: str | None) -> str | None:
def discover_adapters():
"""扫描 domain.adapters.providers 包, 自动注册适配器类型、工厂与配置 schema。"""
from domain.adapters import providers as adapters_pkg
from . import providers as adapters_pkg
TYPE_MAP.clear()
CONFIG_SCHEMAS.clear()

View File

@@ -2,13 +2,13 @@ from typing import Optional
from fastapi import HTTPException
from domain.adapters.registry import (
from domain.auth import User
from .registry import (
get_config_schemas,
normalize_adapter_type,
runtime_registry,
)
from domain.adapters.types import AdapterCreate, AdapterOut
from domain.auth.types import User
from .types import AdapterCreate, AdapterOut
from models import StorageAdapter

View File

@@ -1,4 +1,9 @@
from .api import router
__all__ = ["router"]
from .service import AgentService
from .types import AgentChatContext, AgentChatRequest, PendingToolCall
__all__ = [
"AgentService",
"AgentChatContext",
"AgentChatRequest",
"PendingToolCall",
]

View File

@@ -4,11 +4,10 @@ from fastapi import APIRouter, Depends, Request
from fastapi.responses import StreamingResponse
from api.response import success
from domain.agent.service import AgentService
from domain.agent.types import AgentChatRequest
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.auth import User, get_current_active_user
from .service import AgentService
from .types import AgentChatRequest
router = APIRouter(prefix="/api/agent", tags=["agent"])

View File

@@ -6,11 +6,10 @@ from typing import Any, Dict, List, Optional, Tuple
import httpx
from fastapi import HTTPException
from domain.agent.tools import get_tool, openai_tools, tool_result_to_content
from domain.agent.types import AgentChatRequest, PendingToolCall
from domain.ai.inference import MissingModelError, chat_completion, chat_completion_stream
from domain.ai.service import AIProviderService
from domain.auth.types import User
from domain.ai import AIProviderService, MissingModelError, chat_completion, chat_completion_stream
from domain.auth import User
from .tools import get_tool, openai_tools, tool_result_to_content
from .types import AgentChatRequest, PendingToolCall
def _normalize_path(p: Optional[str]) -> Optional[str]:
@@ -136,6 +135,11 @@ def _sse(event: str, data: Any) -> bytes:
return f"event: {event}\ndata: {payload}\n\n".encode("utf-8")
def _format_exc(exc: BaseException) -> str:
text = str(exc)
return text if text else exc.__class__.__name__
class AgentService:
@classmethod
async def chat(cls, req: AgentChatRequest, user: Optional[User]) -> Dict[str, Any]:
@@ -376,11 +380,11 @@ class AgentService:
if isinstance(msg, dict):
assistant_message = msg
except MissingModelError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
raise HTTPException(status_code=400, detail=_format_exc(exc)) from exc
except httpx.HTTPStatusError as exc:
raise HTTPException(status_code=502, detail=f"对话请求失败: {exc}") from exc
raise HTTPException(status_code=502, detail=f"对话请求失败: {_format_exc(exc)}") from exc
except httpx.RequestError as exc:
raise HTTPException(status_code=502, detail=f"对话请求异常: {exc}") from exc
raise HTTPException(status_code=502, detail=f"对话请求异常: {_format_exc(exc)}") from exc
if not assistant_message:
assistant_message = {"role": "assistant", "content": ""}
@@ -446,3 +450,21 @@ class AgentService:
except asyncio.CancelledError:
return
except HTTPException as exc:
detail = exc.detail
content = detail if isinstance(detail, str) else str(detail)
if not content.strip():
content = f"请求失败({exc.status_code})"
new_messages.append({"role": "assistant", "content": content})
payload: Dict[str, Any] = {"messages": new_messages}
if pending:
payload["pending_tool_calls"] = [p.model_dump() for p in pending]
yield _sse("done", payload)
return
except Exception as exc: # noqa: BLE001
new_messages.append({"role": "assistant", "content": f"服务端异常: {_format_exc(exc)}"})
payload: Dict[str, Any] = {"messages": new_messages}
if pending:
payload["pending_tool_calls"] = [p.model_dump() for p in pending]
yield _sse("done", payload)
return

View File

@@ -2,10 +2,9 @@ import json
from dataclasses import dataclass
from typing import Any, Awaitable, Callable, Dict, List, Optional
from domain.processors.service import ProcessorService
from domain.processors.types import ProcessDirectoryRequest, ProcessRequest
from domain.virtual_fs.service import VirtualFSService
from domain.virtual_fs.search.search_service import VirtualFSSearchService
from domain.processors import ProcessDirectoryRequest, ProcessRequest, ProcessorService
from domain.virtual_fs import VirtualFSService
from domain.virtual_fs.search import VirtualFSSearchService
@dataclass(frozen=True)

View File

@@ -1,28 +1,61 @@
from .api import router_ai, router_vector_db
from .inference import (
MissingModelError,
chat_completion,
chat_completion_stream,
describe_image_base64,
get_text_embedding,
provider_service,
rerank_texts,
)
from .service import (
AIProviderService,
FILE_COLLECTION_NAME,
VECTOR_COLLECTION_NAME,
DEFAULT_VECTOR_DIMENSION,
VectorDBConfigManager,
VectorDBService,
DEFAULT_VECTOR_DIMENSION,
ABILITIES,
normalize_capabilities,
)
from .types import (
ABILITIES,
AIDefaultsUpdate,
AIModelCreate,
AIModelUpdate,
AIProviderCreate,
AIProviderUpdate,
VectorDBConfigPayload,
normalize_capabilities,
)
from .vector_providers import (
BaseVectorProvider,
MilvusLiteProvider,
MilvusServerProvider,
QdrantProvider,
get_provider_class,
get_provider_entry,
list_providers,
)
__all__ = [
"router_ai",
"router_vector_db",
"MissingModelError",
"chat_completion",
"chat_completion_stream",
"describe_image_base64",
"get_text_embedding",
"provider_service",
"rerank_texts",
"AIProviderService",
"VectorDBService",
"VectorDBConfigManager",
"DEFAULT_VECTOR_DIMENSION",
"VECTOR_COLLECTION_NAME",
"FILE_COLLECTION_NAME",
"BaseVectorProvider",
"MilvusLiteProvider",
"MilvusServerProvider",
"QdrantProvider",
"list_providers",
"get_provider_entry",
"get_provider_class",
"ABILITIES",
"normalize_capabilities",
"AIDefaultsUpdate",

View File

@@ -5,8 +5,9 @@ from fastapi import APIRouter, Depends, HTTPException, Path, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.ai.service import AIProviderService, VectorDBConfigManager, VectorDBService
from domain.ai.types import (
from domain.auth import User, get_current_active_user
from .service import AIProviderService, VectorDBConfigManager, VectorDBService
from .types import (
AIDefaultsUpdate,
AIModelCreate,
AIModelUpdate,
@@ -14,9 +15,7 @@ from domain.ai.types import (
AIProviderUpdate,
VectorDBConfigPayload,
)
from domain.ai.vector_providers import get_provider_class, get_provider_entry, list_providers
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from .vector_providers import get_provider_class, get_provider_entry, list_providers
router_ai = APIRouter(prefix="/api/ai", tags=["ai"])
router_vector_db = APIRouter(prefix="/api/vector-db", tags=["vector-db"])

View File

@@ -4,7 +4,7 @@ import httpx
from typing import Any, AsyncIterator, Dict, List, Sequence, Tuple
from models.database import AIModel, AIProvider
from domain.ai.service import AIProviderService
from .service import AIProviderService
provider_service = AIProviderService

View File

@@ -7,7 +7,7 @@ import httpx
from tortoise.exceptions import DoesNotExist
from tortoise.transactions import in_transaction
from domain.config.service import ConfigService
from domain.config import ConfigService
from models.database import AIDefaultModel, AIModel, AIProvider
from .types import ABILITIES, normalize_capabilities

View File

@@ -1,5 +1,4 @@
from domain.audit.decorator import audit
from domain.audit.types import AuditAction
from domain.audit.api import router
from .decorator import audit
from .types import AuditAction
__all__ = ["audit", "AuditAction", "router"]
__all__ = ["audit", "AuditAction"]

View File

@@ -4,10 +4,9 @@ from typing import Annotated, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from api import response
from domain.audit.service import AuditService
from domain.audit.types import AuditAction
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.auth import User, get_current_active_user
from .service import AuditService
from .types import AuditAction
CurrentUser = Annotated[User, Depends(get_current_active_user)]

View File

@@ -7,11 +7,11 @@ import jwt
from fastapi import Request
from jwt.exceptions import InvalidTokenError
from domain.audit.service import AuditService
from domain.audit.types import AuditAction
from domain.auth.service import ALGORITHM
from domain.config.service import ConfigService
from domain.auth import ALGORITHM
from domain.config import ConfigService
from models.database import UserAccount
from .service import AuditService
from .types import AuditAction
def _extract_request(bound_args: Mapping[str, Any]) -> Request | None:

View File

@@ -2,7 +2,7 @@ from typing import Any, Dict, Optional
from models.database import AuditLog
from domain.audit.types import AuditAction
from .types import AuditAction
class AuditService:

49
domain/auth/__init__.py Normal file
View File

@@ -0,0 +1,49 @@
from .service import (
ALGORITHM,
AuthService,
authenticate_user_db,
create_access_token,
get_current_active_user,
get_current_user,
get_password_hash,
has_users,
register_user,
request_password_reset,
reset_password_with_token,
verify_password,
verify_password_reset_token,
)
from .types import (
PasswordResetConfirm,
PasswordResetRequest,
RegisterRequest,
Token,
TokenData,
UpdateMeRequest,
User,
UserInDB,
)
__all__ = [
"ALGORITHM",
"AuthService",
"authenticate_user_db",
"create_access_token",
"get_current_active_user",
"get_current_user",
"get_password_hash",
"has_users",
"register_user",
"request_password_reset",
"reset_password_with_token",
"verify_password",
"verify_password_reset_token",
"PasswordResetConfirm",
"PasswordResetRequest",
"RegisterRequest",
"Token",
"TokenData",
"UpdateMeRequest",
"User",
"UserInDB",
]

View File

@@ -5,8 +5,8 @@ from fastapi.security import OAuth2PasswordRequestForm
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import AuthService, get_current_active_user
from domain.auth.types import (
from .service import AuthService, get_current_active_user
from .types import (
PasswordResetConfirm,
PasswordResetRequest,
RegisterRequest,

View File

@@ -11,7 +11,9 @@ from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt.exceptions import InvalidTokenError
from domain.auth.types import (
from domain.config import ConfigService
from models.database import UserAccount
from .types import (
PasswordResetConfirm,
PasswordResetRequest,
RegisterRequest,
@@ -21,8 +23,6 @@ from domain.auth.types import (
User,
UserInDB,
)
from models.database import UserAccount
from domain.config.service import ConfigService
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 365
@@ -324,7 +324,7 @@ class AuthService:
@classmethod
async def _send_password_reset_email(cls, user: UserAccount, token: str) -> None:
from domain.email.service import EmailService
from domain.email import EmailService
app_domain = await ConfigService.get("APP_DOMAIN", None)
base_url = (app_domain or "http://localhost:5173").rstrip("/")

View File

@@ -1 +1,7 @@
from .service import BackupService
from .types import BackupData
__all__ = [
"BackupService",
"BackupData",
]

View File

@@ -4,8 +4,8 @@ from fastapi import APIRouter, Depends, File, Request, UploadFile
from fastapi.responses import JSONResponse
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.backup.service import BackupService
from domain.auth import get_current_active_user
from .service import BackupService
router = APIRouter(
prefix="/api/backup",

View File

@@ -4,8 +4,8 @@ from datetime import datetime
from fastapi import HTTPException
from tortoise.transactions import in_transaction
from domain.backup.types import BackupData
from domain.config.service import VERSION
from domain.config import VERSION
from .types import BackupData
from models.database import (
AIDefaultModel,
AIModel,

10
domain/config/__init__.py Normal file
View File

@@ -0,0 +1,10 @@
from .service import ConfigService, VERSION
from .types import ConfigItem, LatestVersionInfo, SystemStatus
__all__ = [
"ConfigService",
"VERSION",
"ConfigItem",
"LatestVersionInfo",
"SystemStatus",
]

View File

@@ -4,10 +4,9 @@ from fastapi import APIRouter, Depends, Form, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.config.service import ConfigService
from domain.config.types import ConfigItem
from domain.auth import User, get_current_active_user
from .service import ConfigService
from .types import ConfigItem
router = APIRouter(prefix="/api/config", tags=["config"])

View File

@@ -5,7 +5,7 @@ from typing import Any, Dict, Optional
import httpx
from dotenv import load_dotenv
from domain.config.types import LatestVersionInfo, SystemStatus
from .types import LatestVersionInfo, SystemStatus
from models.database import Configuration, UserAccount
load_dotenv(dotenv_path=".env")

20
domain/email/__init__.py Normal file
View File

@@ -0,0 +1,20 @@
from .service import EmailService, EmailTemplateRenderer
from .types import (
EmailConfig,
EmailSecurity,
EmailSendPayload,
EmailTemplatePreviewPayload,
EmailTemplateUpdate,
EmailTestRequest,
)
__all__ = [
"EmailService",
"EmailTemplateRenderer",
"EmailConfig",
"EmailSecurity",
"EmailSendPayload",
"EmailTemplatePreviewPayload",
"EmailTemplateUpdate",
"EmailTestRequest",
]

View File

@@ -2,10 +2,9 @@ from fastapi import APIRouter, Depends, HTTPException, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.email.service import EmailService, EmailTemplateRenderer
from domain.email.types import (
from domain.auth import User, get_current_active_user
from .service import EmailService, EmailTemplateRenderer
from .types import (
EmailTemplatePreviewPayload,
EmailTemplateUpdate,
EmailTestRequest,

View File

@@ -7,8 +7,8 @@ from pathlib import Path
from string import Template
from typing import Any, Dict, List, Optional
from domain.config.service import ConfigService
from domain.email.types import EmailConfig, EmailSecurity, EmailSendPayload
from domain.config import ConfigService
from .types import EmailConfig, EmailSecurity, EmailSendPayload
class EmailTemplateRenderer:
@@ -104,7 +104,7 @@ class EmailService:
template: str,
context: Optional[Dict[str, Any]] = None,
):
from domain.tasks.task_queue import TaskProgress, task_queue_service
from domain.tasks import TaskProgress, task_queue_service
payload = EmailSendPayload(
recipients=recipients,
@@ -126,7 +126,7 @@ class EmailService:
@classmethod
async def send_from_task(cls, task_id: str, data: Dict[str, Any]):
from domain.tasks.task_queue import TaskProgress, task_queue_service
from domain.tasks import TaskProgress, task_queue_service
payload = EmailSendPayload(**data)

View File

@@ -0,0 +1,7 @@
from .service import OfflineDownloadService
from .types import OfflineDownloadCreate
__all__ = [
"OfflineDownloadService",
"OfflineDownloadCreate",
]

View File

@@ -4,10 +4,9 @@ from fastapi import APIRouter, Depends, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.offline_downloads.service import OfflineDownloadService
from domain.offline_downloads.types import OfflineDownloadCreate
from domain.auth import User, get_current_active_user
from .service import OfflineDownloadService
from .types import OfflineDownloadCreate
CurrentUser = Annotated[User, Depends(get_current_active_user)]

View File

@@ -7,11 +7,10 @@ import aiofiles
import aiohttp
from fastapi import Depends, HTTPException
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.offline_downloads.types import OfflineDownloadCreate
from domain.virtual_fs.service import VirtualFSService
from domain.tasks.task_queue import Task, TaskProgress, task_queue_service
from domain.auth import User, get_current_active_user
from domain.tasks import Task, TaskProgress, task_queue_service
from domain.virtual_fs import VirtualFSService
from .types import OfflineDownloadCreate
class OfflineDownloadService:

View File

@@ -4,9 +4,9 @@ Foxel 插件系统
提供 .foxpkg 插件包的安装、管理和运行时加载功能。
"""
from domain.plugins.loader import PluginLoader, PluginLoadError
from domain.plugins.service import PluginService
from domain.plugins.startup import init_plugins, load_installed_plugins
from .loader import PluginLoadError, PluginLoader
from .service import PluginService
from .startup import init_plugins, load_installed_plugins
__all__ = [
"PluginLoader",

View File

@@ -8,8 +8,8 @@ from fastapi import APIRouter, File, Request, UploadFile
from fastapi.responses import FileResponse
from domain.audit import AuditAction, audit
from domain.plugins.service import PluginService
from domain.plugins.types import (
from .service import PluginService
from .types import (
PluginInstallResult,
PluginOut,
)

View File

@@ -20,7 +20,7 @@ from typing import Any, Dict, List, Optional, Tuple
from fastapi import APIRouter
from domain.plugins.types import (
from .types import (
ManifestProcessorConfig,
ManifestRouteConfig,
PluginManifest,
@@ -344,7 +344,7 @@ class PluginLoader:
supported_exts = getattr(module, "SUPPORTED_EXTS", [])
# 注册到处理器注册表
from domain.processors.registry import CONFIG_SCHEMAS, TYPE_MAP
from domain.processors import CONFIG_SCHEMAS, TYPE_MAP
processor_type = processor_config.type
TYPE_MAP[processor_type] = factory
@@ -401,7 +401,7 @@ class PluginLoader:
"""
# 卸载处理器
if manifest and manifest.backend and manifest.backend.processors:
from domain.processors.registry import CONFIG_SCHEMAS, TYPE_MAP
from domain.processors import CONFIG_SCHEMAS, TYPE_MAP
for proc_config in manifest.backend.processors:
proc_type = proc_config.type

View File

@@ -12,8 +12,8 @@ from typing import List, Optional, Union
from fastapi import HTTPException
from domain.plugins.loader import PluginLoadError, PluginLoader
from domain.plugins.types import (
from .loader import PluginLoadError, PluginLoader
from .types import (
PluginInstallResult,
PluginManifest,
PluginOut,

View File

@@ -7,8 +7,8 @@
import logging
from typing import TYPE_CHECKING, List, Tuple
from domain.plugins.loader import PluginLoadError, PluginLoader
from domain.plugins.types import PluginManifest
from .loader import PluginLoadError, PluginLoader
from .types import PluginManifest
if TYPE_CHECKING:
from fastapi import FastAPI
@@ -113,4 +113,3 @@ async def init_plugins(app: "FastAPI") -> None:
logger.warning(f" - {error}")
else:
logger.info(f"插件加载完成,共 {loaded_count} 个插件")

View File

@@ -0,0 +1,35 @@
from .base import BaseProcessor
from .registry import (
CONFIG_SCHEMAS,
TYPE_MAP,
get_config_schema,
get_config_schemas,
get_last_discovery_errors,
get_module_path,
reload_processors,
)
from .service import (
ProcessorService,
get_processor,
list_processors,
reload_processor_modules,
)
from .types import ProcessDirectoryRequest, ProcessRequest, UpdateSourceRequest
__all__ = [
"BaseProcessor",
"CONFIG_SCHEMAS",
"TYPE_MAP",
"get_config_schema",
"get_config_schemas",
"get_last_discovery_errors",
"get_module_path",
"reload_processors",
"ProcessorService",
"get_processor",
"list_processors",
"reload_processor_modules",
"ProcessDirectoryRequest",
"ProcessRequest",
"UpdateSourceRequest",
]

View File

@@ -4,10 +4,9 @@ from fastapi import APIRouter, Body, Depends, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.processors.service import ProcessorService
from domain.processors.types import (
from domain.auth import User, get_current_active_user
from .service import ProcessorService
from .types import (
ProcessDirectoryRequest,
ProcessRequest,
UpdateSourceRequest,

View File

@@ -8,12 +8,14 @@ from fastapi.responses import Response
from PIL import Image
from ..base import BaseProcessor
from domain.ai.inference import describe_image_base64, get_text_embedding, provider_service
from domain.ai.service import (
VectorDBService,
from domain.ai import (
DEFAULT_VECTOR_DIMENSION,
VECTOR_COLLECTION_NAME,
FILE_COLLECTION_NAME,
VECTOR_COLLECTION_NAME,
VectorDBService,
describe_image_base64,
get_text_embedding,
provider_service,
)

View File

@@ -5,7 +5,7 @@ from pathlib import Path
from types import ModuleType
from typing import Callable, Dict, Optional
from domain.processors.base import BaseProcessor
from .base import BaseProcessor
ProcessorFactory = Callable[[], BaseProcessor]
TYPE_MAP: Dict[str, ProcessorFactory] = {}
@@ -16,7 +16,7 @@ LAST_DISCOVERY_ERRORS: list[str] = []
def discover_processors(force_reload: bool = False) -> list[str]:
"""扫描并缓存可用的处理器模块。"""
from domain.processors import builtin as processors_pkg
from . import builtin as processors_pkg
TYPE_MAP.clear()
CONFIG_SCHEMAS.clear()

View File

@@ -3,20 +3,20 @@ from typing import List, Tuple
from fastapi import HTTPException
from fastapi.concurrency import run_in_threadpool
from domain.processors.registry import (
from domain.tasks import task_queue_service
from domain.virtual_fs import VirtualFSService
from .registry import (
get,
get_config_schema,
get_config_schemas,
get_module_path,
reload_processors,
)
from domain.processors.types import (
from .types import (
ProcessDirectoryRequest,
ProcessRequest,
UpdateSourceRequest,
)
from domain.virtual_fs.service import VirtualFSService
from domain.tasks.task_queue import task_queue_service
class ProcessorService:

View File

@@ -0,0 +1 @@
__all__: list[str] = []

10
domain/share/__init__.py Normal file
View File

@@ -0,0 +1,10 @@
from .service import ShareService
from .types import ShareCreate, ShareInfo, ShareInfoWithPassword, SharePassword
__all__ = [
"ShareService",
"ShareCreate",
"ShareInfo",
"ShareInfoWithPassword",
"SharePassword",
]

View File

@@ -4,10 +4,9 @@ from fastapi import APIRouter, Depends, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.share.service import ShareService
from domain.share.types import (
from domain.auth import User, get_current_active_user
from .service import ShareService
from .types import (
ShareCreate,
ShareInfo,
ShareInfoWithPassword,

View File

@@ -7,7 +7,7 @@ import bcrypt
from fastapi import HTTPException, status
from fastapi.responses import Response
from domain.virtual_fs.service import VirtualFSService
from domain.virtual_fs import VirtualFSService
from models.database import ShareLink, UserAccount

24
domain/tasks/__init__.py Normal file
View File

@@ -0,0 +1,24 @@
from .service import TaskService
from .task_queue import Task, TaskProgress, TaskStatus, task_queue_service
from .types import (
AutomationTaskBase,
AutomationTaskCreate,
AutomationTaskRead,
AutomationTaskUpdate,
TaskQueueSettings,
TaskQueueSettingsResponse,
)
__all__ = [
"TaskService",
"Task",
"TaskProgress",
"TaskStatus",
"task_queue_service",
"AutomationTaskBase",
"AutomationTaskCreate",
"AutomationTaskRead",
"AutomationTaskUpdate",
"TaskQueueSettings",
"TaskQueueSettingsResponse",
]

View File

@@ -2,9 +2,9 @@ from fastapi import APIRouter, Depends, Request
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.tasks.service import TaskService
from domain.tasks.types import (
from domain.auth import get_current_active_user
from .service import TaskService
from .types import (
AutomationTaskCreate,
AutomationTaskUpdate,
TaskQueueSettings,

View File

@@ -3,17 +3,16 @@ from typing import Annotated, Any, Dict, Optional
from fastapi import Depends, HTTPException
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.config.service import ConfigService
from domain.tasks.types import (
from domain.auth import User, get_current_active_user
from domain.config import ConfigService
from .task_queue import task_queue_service
from .types import (
AutomationTaskCreate,
AutomationTaskUpdate,
TaskQueueSettings,
TaskQueueSettingsResponse,
)
from models.database import AutomationTask
from domain.tasks.task_queue import task_queue_service
class TaskService:

View File

@@ -74,7 +74,7 @@ class TaskQueueService:
try:
# Local import to avoid circular dependency during module load.
from domain.virtual_fs.service import VirtualFSService
from domain.virtual_fs import VirtualFSService
if task.name == "process_file":
params = task.task_info
@@ -88,7 +88,7 @@ class TaskQueueService:
task.result = result
elif task.name == "automation_task" or self._is_processor_task(task.name):
from models.database import AutomationTask
from domain.processors.service import get_processor
from domain.processors import get_processor
params = task.task_info
auto_task = await AutomationTask.get(id=params["task_id"])
@@ -116,7 +116,7 @@ class TaskQueueService:
await VirtualFSService.write_file(save_to, result)
task.result = "Automation task completed"
elif task.name == "offline_http_download":
from domain.offline_downloads.service import OfflineDownloadService
from domain.offline_downloads import OfflineDownloadService
result_path = await OfflineDownloadService.run_http_download(task)
task.result = {"path": result_path}
@@ -124,7 +124,7 @@ class TaskQueueService:
result = await VirtualFSService.run_cross_mount_transfer_task(task)
task.result = result
elif task.name == "send_email":
from domain.email.service import EmailService
from domain.email import EmailService
await EmailService.send_from_task(task.id, task.task_info)
task.result = "Email sent"
else:
@@ -141,7 +141,7 @@ class TaskQueueService:
def _is_processor_task(self, task_name: str) -> bool:
try:
from domain.processors.service import get_processor
from domain.processors import get_processor
return get_processor(task_name) is not None
except Exception:
@@ -180,7 +180,7 @@ class TaskQueueService:
async def start_worker(self, concurrency: int | None = None):
if concurrency is None:
from domain.config.service import ConfigService
from domain.config import ConfigService
stored_value = await ConfigService.get("TASK_QUEUE_CONCURRENCY", self._concurrency)
try:

View File

@@ -0,0 +1,11 @@
from .service import VirtualFSService
from .types import DirListing, MkdirRequest, MoveRequest, SearchResultItem, VfsEntry
__all__ = [
"VirtualFSService",
"DirListing",
"MkdirRequest",
"MoveRequest",
"SearchResultItem",
"VfsEntry",
]

View File

@@ -4,10 +4,9 @@ from fastapi import APIRouter, Depends, File, Query, Request, UploadFile
from api.response import success
from domain.audit import AuditAction, audit
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.virtual_fs.service import VirtualFSService
from domain.virtual_fs.types import MkdirRequest, MoveRequest
from domain.auth import User, get_current_active_user
from .service import VirtualFSService
from .types import MkdirRequest, MoveRequest
router = APIRouter(prefix="/api/fs", tags=["virtual-fs"])

View File

@@ -4,8 +4,8 @@ from typing import Any, AsyncIterator, Union
from fastapi import HTTPException
from fastapi.responses import Response
from domain.tasks.service import TaskService
from domain.virtual_fs.thumbnail import is_raw_filename, raw_bytes_to_jpeg
from domain.tasks import TaskService
from .thumbnail import is_raw_filename, raw_bytes_to_jpeg
from .listing import VirtualFSListingMixin

View File

@@ -3,9 +3,9 @@ from typing import Any, Dict, List, Tuple
from fastapi import HTTPException
from api.response import page
from domain.adapters.registry import runtime_registry
from domain.ai.service import VectorDBService, VECTOR_COLLECTION_NAME, FILE_COLLECTION_NAME
from domain.virtual_fs.thumbnail import is_image_filename, is_video_filename
from domain.adapters import runtime_registry
from domain.ai import FILE_COLLECTION_NAME, VECTOR_COLLECTION_NAME, VectorDBService
from .thumbnail import is_image_filename, is_video_filename
from models import StorageAdapter
from .resolver import VirtualFSResolverMixin

View File

@@ -0,0 +1 @@
__all__: list[str] = []

View File

@@ -15,8 +15,8 @@ from fastapi import APIRouter, Request, Response
from fastapi import HTTPException
from domain.audit import AuditAction, audit
from domain.config.service import ConfigService
from domain.virtual_fs.service import VirtualFSService
from domain.config import ConfigService
from domain.virtual_fs import VirtualFSService
router = APIRouter(prefix="/s3", tags=["s3"])

View File

@@ -9,10 +9,9 @@ from fastapi import APIRouter, Request, Response, HTTPException, Depends
import xml.etree.ElementTree as ET
from domain.audit import AuditAction, audit
from domain.auth.service import AuthService
from domain.auth.types import User, UserInDB
from domain.virtual_fs.service import VirtualFSService
from domain.config.service import ConfigService
from domain.auth import AuthService, User, UserInDB
from domain.config import ConfigService
from domain.virtual_fs import VirtualFSService
_WEBDAV_ENABLED_KEY = "WEBDAV_MAPPING_ENABLED"

View File

@@ -16,7 +16,7 @@ class VirtualFSProcessingMixin(VirtualFSTransferMixin):
save_to: str | None = None,
overwrite: bool = False,
) -> Any:
from domain.processors.service import get_processor
from domain.processors import get_processor
processor = get_processor(processor_type)
if not processor:

View File

@@ -3,7 +3,7 @@ from typing import Tuple
from fastapi import HTTPException
from fastapi.responses import Response
from domain.adapters.registry import runtime_registry
from domain.adapters import runtime_registry
from models import StorageAdapter
from .common import VirtualFSCommonMixin

View File

@@ -4,8 +4,8 @@ import re
from fastapi import HTTPException, UploadFile
from fastapi.responses import Response
from domain.config.service import ConfigService
from domain.virtual_fs.thumbnail import (
from domain.config import ConfigService
from .thumbnail import (
get_or_create_thumb,
is_image_filename,
is_raw_filename,

View File

@@ -0,0 +1,3 @@
from .search_service import VirtualFSSearchService
__all__ = ["VirtualFSSearchService"]

View File

@@ -1,9 +1,8 @@
from fastapi import APIRouter, Depends, Query
from api.response import success
from domain.auth.service import get_current_active_user
from domain.auth.types import User
from domain.virtual_fs.search.search_service import VirtualFSSearchService
from domain.auth import User, get_current_active_user
from .search_service import VirtualFSSearchService
router = APIRouter(prefix="/api/fs/search", tags=["search"])

View File

@@ -1,8 +1,7 @@
from typing import Any, Dict, List, Tuple
from domain.virtual_fs.types import SearchResultItem
from domain.ai.inference import get_text_embedding
from domain.ai.service import VectorDBService, VECTOR_COLLECTION_NAME, FILE_COLLECTION_NAME
from domain.ai import FILE_COLLECTION_NAME, VECTOR_COLLECTION_NAME, VectorDBService, get_text_embedding
from ..types import SearchResultItem
def _normalize_result(raw: Dict[str, Any], source: str, fallback_score: float = 0.0) -> SearchResultItem:

View File

@@ -5,7 +5,7 @@ import time
from fastapi import HTTPException
from domain.config.service import ConfigService
from domain.config import ConfigService
from .processing import VirtualFSProcessingMixin

View File

@@ -273,7 +273,7 @@ class VirtualFSTransferMixin(VirtualFSFileOpsMixin):
"overwrite": overwrite,
}
from domain.tasks.task_queue import task_queue_service
from domain.tasks import task_queue_service
task = await task_queue_service.add_task("cross_mount_transfer", payload)
return {
@@ -286,7 +286,7 @@ class VirtualFSTransferMixin(VirtualFSFileOpsMixin):
@classmethod
async def run_cross_mount_transfer_task(cls, task: "Task") -> Dict[str, Any]:
from domain.tasks.task_queue import task_queue_service
from domain.tasks import task_queue_service
params = task.task_info or {}
operation = params.get("operation")

View File

@@ -2,8 +2,8 @@ import os
from pathlib import Path
from contextlib import asynccontextmanager
from domain.config.service import ConfigService, VERSION
from domain.adapters.registry import runtime_registry
from domain.adapters import runtime_registry
from domain.config import ConfigService, VERSION
from db.session import close_db, init_db
from api.routers import include_routers
from fastapi.middleware.cors import CORSMiddleware
@@ -19,7 +19,7 @@ from middleware.exception_handler import (
)
import httpx
from dotenv import load_dotenv
from domain.tasks.task_queue import task_queue_service
from domain.tasks import task_queue_service
load_dotenv()
@@ -59,7 +59,7 @@ async def lifespan(app: FastAPI):
await task_queue_service.start_worker()
# 加载已安装的插件
from domain.plugins.startup import init_plugins
from domain.plugins import init_plugins
await init_plugins(app)
# 在所有路由加载完成后,挂载静态文件服务(放在最后以避免覆盖 API 路由)

View File

@@ -13,8 +13,8 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from domain.auth.service import get_password_hash
from domain.config.service import VERSION
from domain.config import VERSION
from domain.auth import get_password_hash
def _project_root() -> Path: