mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
refactor(sdk): isolate plugin query snapshots
This commit is contained in:
@@ -10,19 +10,20 @@ from typing import Any, Callable, Generic, Protocol, TypeVar, cast
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.application.database import AsyncDatabaseExecutor
|
||||
from app.schemas.history import DownloadHistory, TransferHistory
|
||||
from app.schemas.query import (
|
||||
DownloadHistoryFilter,
|
||||
DownloadHistorySnapshot,
|
||||
QueryPage,
|
||||
QueryPageRequest,
|
||||
SubscribeHistory,
|
||||
SubscriptionFilter,
|
||||
SubscriptionHistoryFilter,
|
||||
SubscriptionHistorySnapshot,
|
||||
SubscriptionSnapshot,
|
||||
TransferHistoryFilter,
|
||||
TransferHistorySnapshot,
|
||||
)
|
||||
from app.schemas.subscribe import Subscribe
|
||||
|
||||
RecordT = TypeVar("RecordT")
|
||||
RecordT = TypeVar("RecordT", covariant=True)
|
||||
DtoT = TypeVar("DtoT", bound=BaseModel)
|
||||
ResultT = TypeVar("ResultT")
|
||||
|
||||
@@ -176,7 +177,7 @@ class DataQueryService:
|
||||
self,
|
||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[Subscribe]:
|
||||
) -> QueryPage[SubscriptionSnapshot]:
|
||||
"""同步分页查询订阅。"""
|
||||
normalized_page = self._page_request(page)
|
||||
normalized_filters = self._filter(filters, SubscriptionFilter)
|
||||
@@ -184,36 +185,35 @@ class DataQueryService:
|
||||
filters=normalized_filters,
|
||||
page=normalized_page,
|
||||
)
|
||||
return self._to_page(Subscribe, normalized_page, rows)
|
||||
return self._to_page(SubscriptionSnapshot, normalized_page, rows)
|
||||
|
||||
async def async_list_subscriptions(
|
||||
self,
|
||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[Subscribe]:
|
||||
) -> QueryPage[SubscriptionSnapshot]:
|
||||
"""异步分页查询订阅,业务规则在数据库 worker 中复用同步入口。"""
|
||||
return await self._async_run(
|
||||
partial(self.list_subscriptions, filters, page)
|
||||
)
|
||||
return await self._async_run(partial(self.list_subscriptions, filters, page))
|
||||
|
||||
def get_subscription(self, subscription_id: int) -> Subscribe | None:
|
||||
def get_subscription(self, subscription_id: int) -> SubscriptionSnapshot | None:
|
||||
"""同步按 ID 查询订阅。"""
|
||||
return self._to_item(
|
||||
Subscribe,
|
||||
SubscriptionSnapshot,
|
||||
self._subscriptions.get_subscription(subscription_id),
|
||||
)
|
||||
|
||||
async def async_get_subscription(self, subscription_id: int) -> Subscribe | None:
|
||||
async def async_get_subscription(
|
||||
self,
|
||||
subscription_id: int,
|
||||
) -> SubscriptionSnapshot | None:
|
||||
"""异步按 ID 查询订阅。"""
|
||||
return await self._async_run(
|
||||
partial(self.get_subscription, subscription_id)
|
||||
)
|
||||
return await self._async_run(partial(self.get_subscription, subscription_id))
|
||||
|
||||
def list_subscription_history(
|
||||
self,
|
||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscribeHistory]:
|
||||
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||
"""同步分页查询订阅完成历史。"""
|
||||
normalized_page = self._page_request(page)
|
||||
normalized_filters = self._filter(filters, SubscriptionHistoryFilter)
|
||||
@@ -221,39 +221,38 @@ class DataQueryService:
|
||||
filters=normalized_filters,
|
||||
page=normalized_page,
|
||||
)
|
||||
return self._to_page(SubscribeHistory, normalized_page, rows)
|
||||
return self._to_page(SubscriptionHistorySnapshot, normalized_page, rows)
|
||||
|
||||
async def async_list_subscription_history(
|
||||
self,
|
||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscribeHistory]:
|
||||
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||
"""异步分页查询订阅完成历史。"""
|
||||
return await self._async_run(
|
||||
partial(self.list_subscription_history, filters, page)
|
||||
)
|
||||
return await self._async_run(partial(self.list_subscription_history, filters, page))
|
||||
|
||||
def get_subscription_history(self, history_id: int) -> SubscribeHistory | None:
|
||||
def get_subscription_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> SubscriptionHistorySnapshot | None:
|
||||
"""同步按 ID 查询订阅完成历史。"""
|
||||
return self._to_item(
|
||||
SubscribeHistory,
|
||||
SubscriptionHistorySnapshot,
|
||||
self._subscriptions.get_subscription_history(history_id),
|
||||
)
|
||||
|
||||
async def async_get_subscription_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> SubscribeHistory | None:
|
||||
) -> SubscriptionHistorySnapshot | None:
|
||||
"""异步按 ID 查询订阅完成历史。"""
|
||||
return await self._async_run(
|
||||
partial(self.get_subscription_history, history_id)
|
||||
)
|
||||
return await self._async_run(partial(self.get_subscription_history, history_id))
|
||||
|
||||
def list_download_history(
|
||||
self,
|
||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[DownloadHistory]:
|
||||
) -> QueryPage[DownloadHistorySnapshot]:
|
||||
"""同步分页查询下载历史。"""
|
||||
normalized_page = self._page_request(page)
|
||||
normalized_filters = self._filter(filters, DownloadHistoryFilter)
|
||||
@@ -261,36 +260,35 @@ class DataQueryService:
|
||||
filters=normalized_filters,
|
||||
page=normalized_page,
|
||||
)
|
||||
return self._to_page(DownloadHistory, normalized_page, rows)
|
||||
return self._to_page(DownloadHistorySnapshot, normalized_page, rows)
|
||||
|
||||
async def async_list_download_history(
|
||||
self,
|
||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[DownloadHistory]:
|
||||
) -> QueryPage[DownloadHistorySnapshot]:
|
||||
"""异步分页查询下载历史。"""
|
||||
return await self._async_run(
|
||||
partial(self.list_download_history, filters, page)
|
||||
)
|
||||
return await self._async_run(partial(self.list_download_history, filters, page))
|
||||
|
||||
def get_download_history(self, history_id: int) -> DownloadHistory | None:
|
||||
def get_download_history(self, history_id: int) -> DownloadHistorySnapshot | None:
|
||||
"""同步按 ID 查询下载历史。"""
|
||||
return self._to_item(
|
||||
DownloadHistory,
|
||||
DownloadHistorySnapshot,
|
||||
self._histories.get_download_history(history_id),
|
||||
)
|
||||
|
||||
async def async_get_download_history(self, history_id: int) -> DownloadHistory | None:
|
||||
async def async_get_download_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> DownloadHistorySnapshot | None:
|
||||
"""异步按 ID 查询下载历史。"""
|
||||
return await self._async_run(
|
||||
partial(self.get_download_history, history_id)
|
||||
)
|
||||
return await self._async_run(partial(self.get_download_history, history_id))
|
||||
|
||||
def list_transfer_history(
|
||||
self,
|
||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[TransferHistory]:
|
||||
) -> QueryPage[TransferHistorySnapshot]:
|
||||
"""同步分页查询整理历史。"""
|
||||
normalized_page = self._page_request(page)
|
||||
normalized_filters = self._filter(filters, TransferHistoryFilter)
|
||||
@@ -298,30 +296,29 @@ class DataQueryService:
|
||||
filters=normalized_filters,
|
||||
page=normalized_page,
|
||||
)
|
||||
return self._to_page(TransferHistory, normalized_page, rows)
|
||||
return self._to_page(TransferHistorySnapshot, normalized_page, rows)
|
||||
|
||||
async def async_list_transfer_history(
|
||||
self,
|
||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[TransferHistory]:
|
||||
) -> QueryPage[TransferHistorySnapshot]:
|
||||
"""异步分页查询整理历史。"""
|
||||
return await self._async_run(
|
||||
partial(self.list_transfer_history, filters, page)
|
||||
)
|
||||
return await self._async_run(partial(self.list_transfer_history, filters, page))
|
||||
|
||||
def get_transfer_history(self, history_id: int) -> TransferHistory | None:
|
||||
def get_transfer_history(self, history_id: int) -> TransferHistorySnapshot | None:
|
||||
"""同步按 ID 查询整理历史。"""
|
||||
return self._to_item(
|
||||
TransferHistory,
|
||||
TransferHistorySnapshot,
|
||||
self._histories.get_transfer_history(history_id),
|
||||
)
|
||||
|
||||
async def async_get_transfer_history(self, history_id: int) -> TransferHistory | None:
|
||||
async def async_get_transfer_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> TransferHistorySnapshot | None:
|
||||
"""异步按 ID 查询整理历史。"""
|
||||
return await self._async_run(
|
||||
partial(self.get_transfer_history, history_id)
|
||||
)
|
||||
return await self._async_run(partial(self.get_transfer_history, history_id))
|
||||
|
||||
|
||||
_configured_data_query_service: DataQueryService | None = None
|
||||
|
||||
@@ -16,23 +16,19 @@ from app.db.models.downloadhistory import DownloadHistory
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.db.models.subscribehistory import SubscribeHistory as SubscribeHistoryModel
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.schemas.history import (
|
||||
DownloadHistory as DownloadHistoryView,
|
||||
)
|
||||
from app.schemas.history import (
|
||||
TransferHistory as TransferHistoryView,
|
||||
)
|
||||
from app.schemas.query import (
|
||||
DownloadHistoryFilter,
|
||||
DownloadHistorySnapshot,
|
||||
QueryPageRequest,
|
||||
QuerySortDirection,
|
||||
QuerySortField,
|
||||
SubscriptionFilter,
|
||||
SubscriptionHistoryFilter,
|
||||
SubscriptionHistorySnapshot,
|
||||
SubscriptionSnapshot,
|
||||
TransferHistoryFilter,
|
||||
TransferHistorySnapshot,
|
||||
)
|
||||
from app.schemas.query import SubscribeHistory as SubscribeHistoryView
|
||||
from app.schemas.subscribe import Subscribe as SubscribeView
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING
|
||||
|
||||
_ModelT = TypeVar("_ModelT", bound=Base)
|
||||
@@ -168,7 +164,7 @@ class SqlAlchemyDataQueryAdapter:
|
||||
*,
|
||||
filters: SubscriptionFilter,
|
||||
page: QueryPageRequest,
|
||||
) -> QueryRows[SubscribeView]:
|
||||
) -> QueryRows[SubscriptionSnapshot]:
|
||||
"""按受控组合条件分页查询当前订阅。"""
|
||||
query = filters
|
||||
conditions = self._identity_conditions(Subscribe, query)
|
||||
@@ -196,16 +192,16 @@ class SqlAlchemyDataQueryAdapter:
|
||||
conditions.append(music_condition)
|
||||
return self._page(
|
||||
model=Subscribe,
|
||||
view_model=SubscribeView,
|
||||
view_model=SubscriptionSnapshot,
|
||||
conditions=conditions,
|
||||
page=page,
|
||||
)
|
||||
|
||||
def get_subscription(self, subscription_id: int) -> SubscribeView | None:
|
||||
def get_subscription(self, subscription_id: int) -> SubscriptionSnapshot | None:
|
||||
"""按主键查询订阅并返回脱离 Session 的 DTO。"""
|
||||
return self._get(
|
||||
model=Subscribe,
|
||||
view_model=SubscribeView,
|
||||
view_model=SubscriptionSnapshot,
|
||||
record_id=subscription_id,
|
||||
)
|
||||
|
||||
@@ -214,7 +210,7 @@ class SqlAlchemyDataQueryAdapter:
|
||||
*,
|
||||
filters: SubscriptionHistoryFilter,
|
||||
page: QueryPageRequest,
|
||||
) -> QueryRows[SubscribeHistoryView]:
|
||||
) -> QueryRows[SubscriptionHistorySnapshot]:
|
||||
"""按受控组合条件分页查询订阅完成历史。"""
|
||||
query = filters
|
||||
conditions = self._identity_conditions(SubscribeHistoryModel, query)
|
||||
@@ -242,7 +238,7 @@ class SqlAlchemyDataQueryAdapter:
|
||||
conditions.append(music_condition)
|
||||
return self._page(
|
||||
model=SubscribeHistoryModel,
|
||||
view_model=SubscribeHistoryView,
|
||||
view_model=SubscriptionHistorySnapshot,
|
||||
conditions=conditions,
|
||||
page=page,
|
||||
)
|
||||
@@ -250,11 +246,11 @@ class SqlAlchemyDataQueryAdapter:
|
||||
def get_subscription_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> SubscribeHistoryView | None:
|
||||
) -> SubscriptionHistorySnapshot | None:
|
||||
"""按主键查询订阅完成历史并返回稳定 DTO。"""
|
||||
return self._get(
|
||||
model=SubscribeHistoryModel,
|
||||
view_model=SubscribeHistoryView,
|
||||
view_model=SubscriptionHistorySnapshot,
|
||||
record_id=history_id,
|
||||
)
|
||||
|
||||
@@ -263,7 +259,7 @@ class SqlAlchemyDataQueryAdapter:
|
||||
*,
|
||||
filters: DownloadHistoryFilter,
|
||||
page: QueryPageRequest,
|
||||
) -> QueryRows[DownloadHistoryView]:
|
||||
) -> QueryRows[DownloadHistorySnapshot]:
|
||||
"""按受控组合条件分页查询下载历史。"""
|
||||
query = filters
|
||||
conditions = self._identity_conditions(DownloadHistory, query)
|
||||
@@ -276,20 +272,23 @@ class SqlAlchemyDataQueryAdapter:
|
||||
conditions.append(DownloadHistory.type.in_(media_types))
|
||||
for column, value in (
|
||||
(DownloadHistory.title, query.title),
|
||||
(DownloadHistory.path, query.path),
|
||||
):
|
||||
if value:
|
||||
conditions.append(_contains(column, value))
|
||||
for column, value in (
|
||||
(DownloadHistory.year, query.year),
|
||||
(DownloadHistory.seasons, query.seasons),
|
||||
(DownloadHistory.episodes, query.episodes),
|
||||
(DownloadHistory.path, query.path),
|
||||
(DownloadHistory.download_hash, query.download_hash),
|
||||
(DownloadHistory.username, query.username),
|
||||
(DownloadHistory.episode_group, query.episode_group),
|
||||
):
|
||||
if value is not None and value != "":
|
||||
conditions.append(column == value)
|
||||
if query.text:
|
||||
conditions.append(
|
||||
or_(
|
||||
_contains(DownloadHistory.title, query.text),
|
||||
_contains(DownloadHistory.path, query.text),
|
||||
)
|
||||
)
|
||||
if usernames:
|
||||
conditions.append(DownloadHistory.username.in_(usernames))
|
||||
music_condition = _music_type_condition(DownloadHistory.music_type, query.music_type)
|
||||
@@ -297,16 +296,16 @@ class SqlAlchemyDataQueryAdapter:
|
||||
conditions.append(music_condition)
|
||||
return self._page(
|
||||
model=DownloadHistory,
|
||||
view_model=DownloadHistoryView,
|
||||
view_model=DownloadHistorySnapshot,
|
||||
conditions=conditions,
|
||||
page=page,
|
||||
)
|
||||
|
||||
def get_download_history(self, history_id: int) -> DownloadHistoryView | None:
|
||||
def get_download_history(self, history_id: int) -> DownloadHistorySnapshot | None:
|
||||
"""按主键查询下载历史并返回稳定 DTO。"""
|
||||
return self._get(
|
||||
model=DownloadHistory,
|
||||
view_model=DownloadHistoryView,
|
||||
view_model=DownloadHistorySnapshot,
|
||||
record_id=history_id,
|
||||
)
|
||||
|
||||
@@ -315,7 +314,7 @@ class SqlAlchemyDataQueryAdapter:
|
||||
*,
|
||||
filters: TransferHistoryFilter,
|
||||
page: QueryPageRequest,
|
||||
) -> QueryRows[TransferHistoryView]:
|
||||
) -> QueryRows[TransferHistorySnapshot]:
|
||||
"""按受控组合条件分页查询整理历史。"""
|
||||
query = filters
|
||||
conditions = self._identity_conditions(TransferHistory, query)
|
||||
@@ -331,7 +330,7 @@ class SqlAlchemyDataQueryAdapter:
|
||||
if query.require_media_identity:
|
||||
conditions.extend(self._require_media_identity(TransferHistory))
|
||||
if query.title:
|
||||
conditions.append(_contains(TransferHistory.title, query.title))
|
||||
conditions.append(TransferHistory.title == query.title)
|
||||
if query.text:
|
||||
conditions.append(
|
||||
or_(
|
||||
@@ -358,16 +357,16 @@ class SqlAlchemyDataQueryAdapter:
|
||||
conditions.append(music_condition)
|
||||
return self._page(
|
||||
model=TransferHistory,
|
||||
view_model=TransferHistoryView,
|
||||
view_model=TransferHistorySnapshot,
|
||||
conditions=conditions,
|
||||
page=page,
|
||||
)
|
||||
|
||||
def get_transfer_history(self, history_id: int) -> TransferHistoryView | None:
|
||||
def get_transfer_history(self, history_id: int) -> TransferHistorySnapshot | None:
|
||||
"""按主键查询整理历史并返回稳定 DTO。"""
|
||||
return self._get(
|
||||
model=TransferHistory,
|
||||
view_model=TransferHistoryView,
|
||||
view_model=TransferHistorySnapshot,
|
||||
record_id=history_id,
|
||||
)
|
||||
|
||||
|
||||
+164
-18
@@ -1,14 +1,12 @@
|
||||
"""插件只读数据查询使用的稳定筛选、分页与数据投影合同。"""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Generic, Optional, TypeVar
|
||||
from typing import Any, Generic, Optional, TypeVar
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.history import DownloadHistory, TransferHistory
|
||||
from app.schemas.media import OptionalMediaIdentityMixin
|
||||
from app.schemas.subscribe import Subscribe
|
||||
from app.schemas.media import OptionalMediaIdentityMixin, normalize_media_source
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
T = TypeVar("T")
|
||||
@@ -17,7 +15,7 @@ DEFAULT_QUERY_PAGE_SIZE = 50
|
||||
MAX_QUERY_PAGE_SIZE = 200
|
||||
|
||||
|
||||
class _QueryInput(BaseModel):
|
||||
class _QueryInput(BaseModel): # type: ignore[misc] # Pydantic imports are skipped by strict mypy
|
||||
"""查询输入共同的严格字段合同。"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
@@ -56,7 +54,7 @@ class QueryPageRequest(_QueryInput):
|
||||
sort: QuerySort = Field(default_factory=QuerySort)
|
||||
|
||||
|
||||
class QueryPage(BaseModel, Generic[T]):
|
||||
class QueryPage(BaseModel, Generic[T]): # type: ignore[misc] # Pydantic imports are skipped by strict mypy
|
||||
"""公开查询返回的分页 DTO。"""
|
||||
|
||||
items: list[T] = Field(default_factory=list)
|
||||
@@ -74,23 +72,105 @@ class QueryPage(BaseModel, Generic[T]):
|
||||
return self.page * self.count < self.total
|
||||
|
||||
|
||||
class MediaIdentityQuery(OptionalMediaIdentityMixin, _QueryInput):
|
||||
class MediaIdentityQuery(
|
||||
OptionalMediaIdentityMixin,
|
||||
_QueryInput,
|
||||
):
|
||||
"""允许省略身份,但显式筛选时要求来源与原生 ID 成对有效。"""
|
||||
|
||||
media_source: Optional[MediaSource] = None
|
||||
media_id: Optional[str] = None
|
||||
|
||||
|
||||
class SubscribeHistory(OptionalMediaIdentityMixin, BaseModel):
|
||||
"""订阅完成历史的稳定只读投影。"""
|
||||
class _QuerySnapshot(BaseModel): # type: ignore[misc] # Pydantic imports are skipped by strict mypy
|
||||
"""只读查询 DTO 的媒体身份与脏数据归一化边界。"""
|
||||
|
||||
media_source: Optional[MediaSource] = None
|
||||
media_id: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@field_validator("media_source", mode="before") # type: ignore[misc]
|
||||
@classmethod
|
||||
def _normalize_source(cls, value: Any) -> Optional[MediaSource]:
|
||||
"""未知旧来源不应使整页查询失败,统一降级为空身份。"""
|
||||
return normalize_media_source(value)
|
||||
|
||||
@model_validator(mode="after") # type: ignore[misc]
|
||||
def _normalize_identity_pair(self) -> "_QuerySnapshot":
|
||||
"""输出中的脏半对身份按无身份处理,避免向插件传播不可用主键。"""
|
||||
normalized_id = str(self.media_id).strip() if self.media_id is not None else ""
|
||||
if not self.media_source or not normalized_id or normalized_id == "0":
|
||||
object.__setattr__(self, "media_source", None)
|
||||
object.__setattr__(self, "media_id", None)
|
||||
else:
|
||||
object.__setattr__(self, "media_id", normalized_id)
|
||||
return self
|
||||
|
||||
|
||||
class SubscriptionSnapshot(_QuerySnapshot):
|
||||
"""当前订阅的稳定只读快照,不包含 ORM 或写入行为。"""
|
||||
|
||||
id: int
|
||||
name: Optional[str] = None
|
||||
year: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
keyword: Optional[str] = None
|
||||
music_type: Optional[str] = None
|
||||
total_tracks: Optional[int] = None
|
||||
season: Optional[int] = None
|
||||
poster: Optional[str] = None
|
||||
backdrop: Optional[str] = None
|
||||
vote: Optional[float] = None
|
||||
description: Optional[str] = None
|
||||
# 资源选择规则与质量约束均按宿主保存值只读返回。
|
||||
filter: Optional[str] = None
|
||||
include: Optional[str] = None
|
||||
exclude: Optional[str] = None
|
||||
quality: Optional[str] = None
|
||||
resolution: Optional[str] = None
|
||||
effect: Optional[str] = None
|
||||
audio_quality: Optional[str] = None
|
||||
audio_format: Optional[str] = None
|
||||
min_bitrate: Optional[int] = None
|
||||
min_bit_depth: Optional[int] = None
|
||||
min_sample_rate: Optional[int] = None
|
||||
# 订阅进度字段由宿主维护,插件查询不得据此直接写库。
|
||||
total_episode: Optional[int] = None
|
||||
start_episode: Optional[int] = None
|
||||
lack_episode: Optional[int] = None
|
||||
note: Optional[JsonData] = None
|
||||
state: Optional[str] = None
|
||||
last_update: Optional[str] = None
|
||||
date: Optional[str] = None
|
||||
username: Optional[str] = None
|
||||
sites: Optional[list[int]] = None
|
||||
downloader: Optional[str] = None
|
||||
best_version: Optional[int] = None
|
||||
best_version_full: Optional[int] = None
|
||||
current_priority: Optional[int] = None
|
||||
current_audio_format: Optional[str] = None
|
||||
current_bitrate: Optional[int] = None
|
||||
current_bit_depth: Optional[int] = None
|
||||
current_sample_rate: Optional[int] = None
|
||||
episode_priority: Optional[dict[str, int]] = None
|
||||
save_path: Optional[str] = None
|
||||
search_imdbid: Optional[int] = None
|
||||
manual_total_episode: Optional[int] = None
|
||||
custom_words: Optional[str] = None
|
||||
media_category: Optional[str] = None
|
||||
filter_groups: Optional[list[str]] = None
|
||||
episode_group: Optional[str] = None
|
||||
|
||||
|
||||
class SubscriptionHistorySnapshot(_QuerySnapshot):
|
||||
"""订阅完成历史的稳定只读快照。"""
|
||||
|
||||
id: int
|
||||
name: Optional[str] = None
|
||||
year: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
keyword: Optional[str] = None
|
||||
media_source: Optional[MediaSource] = None
|
||||
media_id: Optional[str] = None
|
||||
music_type: Optional[str] = None
|
||||
total_tracks: Optional[int] = None
|
||||
season: Optional[int] = None
|
||||
@@ -124,13 +204,78 @@ class SubscribeHistory(OptionalMediaIdentityMixin, BaseModel):
|
||||
episode_priority: Optional[dict[str, int]] = None
|
||||
save_path: Optional[str] = None
|
||||
search_imdbid: Optional[int] = None
|
||||
note: Optional[JsonData] = None
|
||||
custom_words: Optional[str] = None
|
||||
media_category: Optional[str] = None
|
||||
filter_groups: Optional[list[str]] = None
|
||||
episode_group: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class DownloadHistorySnapshot(_QuerySnapshot):
|
||||
"""下载历史的稳定只读快照。"""
|
||||
|
||||
id: int
|
||||
path: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
year: Optional[str] = None
|
||||
music_type: Optional[str] = None
|
||||
seasons: Optional[str] = None
|
||||
episodes: Optional[str] = None
|
||||
image: Optional[str] = None
|
||||
poster: Optional[str] = None
|
||||
downloader: Optional[str] = None
|
||||
download_hash: Optional[str] = None
|
||||
torrent_name: Optional[str] = None
|
||||
torrent_description: Optional[str] = None
|
||||
torrent_site: Optional[str] = None
|
||||
userid: Optional[str] = None
|
||||
username: Optional[str] = None
|
||||
channel: Optional[str] = None
|
||||
date: Optional[str] = None
|
||||
note: Optional[JsonData] = None
|
||||
media_category: Optional[str] = None
|
||||
episode_group: Optional[str] = None
|
||||
custom_words: Optional[str] = None
|
||||
|
||||
|
||||
class TransferHistorySnapshot(_QuerySnapshot):
|
||||
"""整理历史的稳定只读快照;内部任务结算标识不会暴露给插件。"""
|
||||
|
||||
id: int
|
||||
src: Optional[str] = None
|
||||
src_storage: Optional[str] = None
|
||||
src_fileitem: Optional[JsonData] = None
|
||||
dest: Optional[str] = None
|
||||
dest_storage: Optional[str] = None
|
||||
dest_fileitem: Optional[JsonData] = None
|
||||
mode: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
year: Optional[str] = None
|
||||
music_type: Optional[str] = None
|
||||
total_tracks: Optional[int] = None
|
||||
audio_format: Optional[str] = None
|
||||
audio_lossless: Optional[bool] = None
|
||||
bit_depth: Optional[int] = None
|
||||
sample_rate: Optional[int] = None
|
||||
bitrate: Optional[int] = None
|
||||
seasons: Optional[str] = None
|
||||
episodes: Optional[str] = None
|
||||
image: Optional[str] = None
|
||||
downloader: Optional[str] = None
|
||||
download_hash: Optional[str] = None
|
||||
status: bool = False
|
||||
errmsg: Optional[str] = None
|
||||
date: Optional[str] = None
|
||||
files: Optional[JsonData] = None
|
||||
episode_group: Optional[str] = None
|
||||
|
||||
@field_validator("status", mode="before") # type: ignore[misc]
|
||||
@classmethod
|
||||
def _normalize_status(cls, value: object) -> bool:
|
||||
"""旧记录的 NULL 状态按失败处理,避免错误地向插件声明整理成功。"""
|
||||
return bool(value)
|
||||
|
||||
|
||||
class SubscriptionFilter(MediaIdentityQuery):
|
||||
@@ -164,6 +309,7 @@ class DownloadHistoryFilter(MediaIdentityQuery):
|
||||
ids: tuple[int, ...] = ()
|
||||
media_types: tuple[MediaType, ...] = ()
|
||||
title: Optional[str] = None
|
||||
text: Optional[str] = None
|
||||
year: Optional[str] = None
|
||||
seasons: Optional[str] = None
|
||||
episodes: Optional[str] = None
|
||||
@@ -198,18 +344,18 @@ class TransferHistoryFilter(MediaIdentityQuery):
|
||||
__all__ = [
|
||||
"DEFAULT_QUERY_PAGE_SIZE",
|
||||
"MAX_QUERY_PAGE_SIZE",
|
||||
"DownloadHistory",
|
||||
"DownloadHistoryFilter",
|
||||
"DownloadHistorySnapshot",
|
||||
"MediaIdentityQuery",
|
||||
"QueryPage",
|
||||
"QueryPageRequest",
|
||||
"QuerySort",
|
||||
"QuerySortDirection",
|
||||
"QuerySortField",
|
||||
"Subscribe",
|
||||
"SubscribeHistory",
|
||||
"SubscriptionFilter",
|
||||
"SubscriptionHistorySnapshot",
|
||||
"SubscriptionHistoryFilter",
|
||||
"TransferHistory",
|
||||
"SubscriptionSnapshot",
|
||||
"TransferHistoryFilter",
|
||||
"TransferHistorySnapshot",
|
||||
]
|
||||
|
||||
+127
-27
@@ -2,41 +2,133 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, Protocol, cast
|
||||
|
||||
from app.schemas.query import (
|
||||
DEFAULT_QUERY_PAGE_SIZE,
|
||||
MAX_QUERY_PAGE_SIZE,
|
||||
DownloadHistory,
|
||||
DownloadHistoryFilter,
|
||||
DownloadHistorySnapshot,
|
||||
MediaIdentityQuery,
|
||||
QueryPage,
|
||||
QueryPageRequest,
|
||||
QuerySort,
|
||||
QuerySortDirection,
|
||||
QuerySortField,
|
||||
Subscribe,
|
||||
SubscribeHistory,
|
||||
SubscriptionFilter,
|
||||
SubscriptionHistoryFilter,
|
||||
TransferHistory,
|
||||
SubscriptionHistorySnapshot,
|
||||
SubscriptionSnapshot,
|
||||
TransferHistoryFilter,
|
||||
TransferHistorySnapshot,
|
||||
)
|
||||
|
||||
|
||||
def _service() -> Any:
|
||||
class _DataQueryBackend(Protocol):
|
||||
"""SDK 转发所需的最小类型合同,不向插件公开应用服务实现。"""
|
||||
|
||||
def list_subscriptions(
|
||||
self,
|
||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscriptionSnapshot]: ...
|
||||
|
||||
async def async_list_subscriptions(
|
||||
self,
|
||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscriptionSnapshot]: ...
|
||||
|
||||
def get_subscription(
|
||||
self,
|
||||
subscription_id: int,
|
||||
) -> SubscriptionSnapshot | None: ...
|
||||
|
||||
async def async_get_subscription(
|
||||
self,
|
||||
subscription_id: int,
|
||||
) -> SubscriptionSnapshot | None: ...
|
||||
|
||||
def list_subscription_history(
|
||||
self,
|
||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscriptionHistorySnapshot]: ...
|
||||
|
||||
async def async_list_subscription_history(
|
||||
self,
|
||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscriptionHistorySnapshot]: ...
|
||||
|
||||
def get_subscription_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> SubscriptionHistorySnapshot | None: ...
|
||||
|
||||
async def async_get_subscription_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> SubscriptionHistorySnapshot | None: ...
|
||||
|
||||
def list_download_history(
|
||||
self,
|
||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[DownloadHistorySnapshot]: ...
|
||||
|
||||
async def async_list_download_history(
|
||||
self,
|
||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[DownloadHistorySnapshot]: ...
|
||||
|
||||
def get_download_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> DownloadHistorySnapshot | None: ...
|
||||
|
||||
async def async_get_download_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> DownloadHistorySnapshot | None: ...
|
||||
|
||||
def list_transfer_history(
|
||||
self,
|
||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[TransferHistorySnapshot]: ...
|
||||
|
||||
async def async_list_transfer_history(
|
||||
self,
|
||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[TransferHistorySnapshot]: ...
|
||||
|
||||
def get_transfer_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> TransferHistorySnapshot | None: ...
|
||||
|
||||
async def async_get_transfer_history(
|
||||
self,
|
||||
history_id: int,
|
||||
) -> TransferHistorySnapshot | None: ...
|
||||
|
||||
|
||||
def _service() -> _DataQueryBackend:
|
||||
"""获取启动阶段登记的查询服务,避免把应用服务暴露为 SDK 合同。"""
|
||||
from app.application.data_query import (
|
||||
get_configured_data_query_service,
|
||||
)
|
||||
|
||||
return get_configured_data_query_service()
|
||||
return cast(_DataQueryBackend, get_configured_data_query_service())
|
||||
|
||||
|
||||
def list_subscriptions(
|
||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[Subscribe]:
|
||||
) -> QueryPage[SubscriptionSnapshot]:
|
||||
"""同步分页读取订阅 DTO。"""
|
||||
return _service().list_subscriptions(filters, page)
|
||||
|
||||
@@ -44,17 +136,19 @@ def list_subscriptions(
|
||||
async def async_list_subscriptions(
|
||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[Subscribe]:
|
||||
) -> QueryPage[SubscriptionSnapshot]:
|
||||
"""异步分页读取订阅 DTO。"""
|
||||
return await _service().async_list_subscriptions(filters, page)
|
||||
|
||||
|
||||
def get_subscription(subscription_id: int) -> Subscribe | None:
|
||||
def get_subscription(subscription_id: int) -> SubscriptionSnapshot | None:
|
||||
"""同步按 ID 读取订阅 DTO。"""
|
||||
return _service().get_subscription(subscription_id)
|
||||
|
||||
|
||||
async def async_get_subscription(subscription_id: int) -> Subscribe | None:
|
||||
async def async_get_subscription(
|
||||
subscription_id: int,
|
||||
) -> SubscriptionSnapshot | None:
|
||||
"""异步按 ID 读取订阅 DTO。"""
|
||||
return await _service().async_get_subscription(subscription_id)
|
||||
|
||||
@@ -62,7 +156,7 @@ async def async_get_subscription(subscription_id: int) -> Subscribe | None:
|
||||
def list_subscription_history(
|
||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscribeHistory]:
|
||||
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||
"""同步分页读取订阅完成历史 DTO。"""
|
||||
return _service().list_subscription_history(filters, page)
|
||||
|
||||
@@ -70,17 +164,19 @@ def list_subscription_history(
|
||||
async def async_list_subscription_history(
|
||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[SubscribeHistory]:
|
||||
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||
"""异步分页读取订阅完成历史 DTO。"""
|
||||
return await _service().async_list_subscription_history(filters, page)
|
||||
|
||||
|
||||
def get_subscription_history(history_id: int) -> SubscribeHistory | None:
|
||||
def get_subscription_history(history_id: int) -> SubscriptionHistorySnapshot | None:
|
||||
"""同步按 ID 读取订阅完成历史 DTO。"""
|
||||
return _service().get_subscription_history(history_id)
|
||||
|
||||
|
||||
async def async_get_subscription_history(history_id: int) -> SubscribeHistory | None:
|
||||
async def async_get_subscription_history(
|
||||
history_id: int,
|
||||
) -> SubscriptionHistorySnapshot | None:
|
||||
"""异步按 ID 读取订阅完成历史 DTO。"""
|
||||
return await _service().async_get_subscription_history(history_id)
|
||||
|
||||
@@ -88,7 +184,7 @@ async def async_get_subscription_history(history_id: int) -> SubscribeHistory |
|
||||
def list_download_history(
|
||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[DownloadHistory]:
|
||||
) -> QueryPage[DownloadHistorySnapshot]:
|
||||
"""同步分页读取下载历史 DTO。"""
|
||||
return _service().list_download_history(filters, page)
|
||||
|
||||
@@ -96,17 +192,19 @@ def list_download_history(
|
||||
async def async_list_download_history(
|
||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[DownloadHistory]:
|
||||
) -> QueryPage[DownloadHistorySnapshot]:
|
||||
"""异步分页读取下载历史 DTO。"""
|
||||
return await _service().async_list_download_history(filters, page)
|
||||
|
||||
|
||||
def get_download_history(history_id: int) -> DownloadHistory | None:
|
||||
def get_download_history(history_id: int) -> DownloadHistorySnapshot | None:
|
||||
"""同步按 ID 读取下载历史 DTO。"""
|
||||
return _service().get_download_history(history_id)
|
||||
|
||||
|
||||
async def async_get_download_history(history_id: int) -> DownloadHistory | None:
|
||||
async def async_get_download_history(
|
||||
history_id: int,
|
||||
) -> DownloadHistorySnapshot | None:
|
||||
"""异步按 ID 读取下载历史 DTO。"""
|
||||
return await _service().async_get_download_history(history_id)
|
||||
|
||||
@@ -114,7 +212,7 @@ async def async_get_download_history(history_id: int) -> DownloadHistory | None:
|
||||
def list_transfer_history(
|
||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[TransferHistory]:
|
||||
) -> QueryPage[TransferHistorySnapshot]:
|
||||
"""同步分页读取整理历史 DTO。"""
|
||||
return _service().list_transfer_history(filters, page)
|
||||
|
||||
@@ -122,17 +220,19 @@ def list_transfer_history(
|
||||
async def async_list_transfer_history(
|
||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||
) -> QueryPage[TransferHistory]:
|
||||
) -> QueryPage[TransferHistorySnapshot]:
|
||||
"""异步分页读取整理历史 DTO。"""
|
||||
return await _service().async_list_transfer_history(filters, page)
|
||||
|
||||
|
||||
def get_transfer_history(history_id: int) -> TransferHistory | None:
|
||||
def get_transfer_history(history_id: int) -> TransferHistorySnapshot | None:
|
||||
"""同步按 ID 读取整理历史 DTO。"""
|
||||
return _service().get_transfer_history(history_id)
|
||||
|
||||
|
||||
async def async_get_transfer_history(history_id: int) -> TransferHistory | None:
|
||||
async def async_get_transfer_history(
|
||||
history_id: int,
|
||||
) -> TransferHistorySnapshot | None:
|
||||
"""异步按 ID 读取整理历史 DTO。"""
|
||||
return await _service().async_get_transfer_history(history_id)
|
||||
|
||||
@@ -140,20 +240,20 @@ async def async_get_transfer_history(history_id: int) -> TransferHistory | None:
|
||||
__all__ = [
|
||||
"DEFAULT_QUERY_PAGE_SIZE",
|
||||
"MAX_QUERY_PAGE_SIZE",
|
||||
"DownloadHistory",
|
||||
"DownloadHistoryFilter",
|
||||
"DownloadHistorySnapshot",
|
||||
"MediaIdentityQuery",
|
||||
"QueryPage",
|
||||
"QueryPageRequest",
|
||||
"QuerySort",
|
||||
"QuerySortDirection",
|
||||
"QuerySortField",
|
||||
"Subscribe",
|
||||
"SubscribeHistory",
|
||||
"SubscriptionFilter",
|
||||
"SubscriptionHistoryFilter",
|
||||
"TransferHistory",
|
||||
"SubscriptionHistorySnapshot",
|
||||
"SubscriptionSnapshot",
|
||||
"TransferHistoryFilter",
|
||||
"TransferHistorySnapshot",
|
||||
"async_get_download_history",
|
||||
"async_get_subscription",
|
||||
"async_get_subscription_history",
|
||||
|
||||
Reference in New Issue
Block a user