mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +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 pydantic import BaseModel
|
||||||
|
|
||||||
from app.application.database import AsyncDatabaseExecutor
|
from app.application.database import AsyncDatabaseExecutor
|
||||||
from app.schemas.history import DownloadHistory, TransferHistory
|
|
||||||
from app.schemas.query import (
|
from app.schemas.query import (
|
||||||
DownloadHistoryFilter,
|
DownloadHistoryFilter,
|
||||||
|
DownloadHistorySnapshot,
|
||||||
QueryPage,
|
QueryPage,
|
||||||
QueryPageRequest,
|
QueryPageRequest,
|
||||||
SubscribeHistory,
|
|
||||||
SubscriptionFilter,
|
SubscriptionFilter,
|
||||||
SubscriptionHistoryFilter,
|
SubscriptionHistoryFilter,
|
||||||
|
SubscriptionHistorySnapshot,
|
||||||
|
SubscriptionSnapshot,
|
||||||
TransferHistoryFilter,
|
TransferHistoryFilter,
|
||||||
|
TransferHistorySnapshot,
|
||||||
)
|
)
|
||||||
from app.schemas.subscribe import Subscribe
|
|
||||||
|
|
||||||
RecordT = TypeVar("RecordT")
|
RecordT = TypeVar("RecordT", covariant=True)
|
||||||
DtoT = TypeVar("DtoT", bound=BaseModel)
|
DtoT = TypeVar("DtoT", bound=BaseModel)
|
||||||
ResultT = TypeVar("ResultT")
|
ResultT = TypeVar("ResultT")
|
||||||
|
|
||||||
@@ -176,7 +177,7 @@ class DataQueryService:
|
|||||||
self,
|
self,
|
||||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[Subscribe]:
|
) -> QueryPage[SubscriptionSnapshot]:
|
||||||
"""同步分页查询订阅。"""
|
"""同步分页查询订阅。"""
|
||||||
normalized_page = self._page_request(page)
|
normalized_page = self._page_request(page)
|
||||||
normalized_filters = self._filter(filters, SubscriptionFilter)
|
normalized_filters = self._filter(filters, SubscriptionFilter)
|
||||||
@@ -184,36 +185,35 @@ class DataQueryService:
|
|||||||
filters=normalized_filters,
|
filters=normalized_filters,
|
||||||
page=normalized_page,
|
page=normalized_page,
|
||||||
)
|
)
|
||||||
return self._to_page(Subscribe, normalized_page, rows)
|
return self._to_page(SubscriptionSnapshot, normalized_page, rows)
|
||||||
|
|
||||||
async def async_list_subscriptions(
|
async def async_list_subscriptions(
|
||||||
self,
|
self,
|
||||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[Subscribe]:
|
) -> QueryPage[SubscriptionSnapshot]:
|
||||||
"""异步分页查询订阅,业务规则在数据库 worker 中复用同步入口。"""
|
"""异步分页查询订阅,业务规则在数据库 worker 中复用同步入口。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.list_subscriptions, filters, page))
|
||||||
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 查询订阅。"""
|
"""同步按 ID 查询订阅。"""
|
||||||
return self._to_item(
|
return self._to_item(
|
||||||
Subscribe,
|
SubscriptionSnapshot,
|
||||||
self._subscriptions.get_subscription(subscription_id),
|
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 查询订阅。"""
|
"""异步按 ID 查询订阅。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.get_subscription, subscription_id))
|
||||||
partial(self.get_subscription, subscription_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
def list_subscription_history(
|
def list_subscription_history(
|
||||||
self,
|
self,
|
||||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[SubscribeHistory]:
|
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||||
"""同步分页查询订阅完成历史。"""
|
"""同步分页查询订阅完成历史。"""
|
||||||
normalized_page = self._page_request(page)
|
normalized_page = self._page_request(page)
|
||||||
normalized_filters = self._filter(filters, SubscriptionHistoryFilter)
|
normalized_filters = self._filter(filters, SubscriptionHistoryFilter)
|
||||||
@@ -221,39 +221,38 @@ class DataQueryService:
|
|||||||
filters=normalized_filters,
|
filters=normalized_filters,
|
||||||
page=normalized_page,
|
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(
|
async def async_list_subscription_history(
|
||||||
self,
|
self,
|
||||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[SubscribeHistory]:
|
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||||
"""异步分页查询订阅完成历史。"""
|
"""异步分页查询订阅完成历史。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.list_subscription_history, filters, page))
|
||||||
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 查询订阅完成历史。"""
|
"""同步按 ID 查询订阅完成历史。"""
|
||||||
return self._to_item(
|
return self._to_item(
|
||||||
SubscribeHistory,
|
SubscriptionHistorySnapshot,
|
||||||
self._subscriptions.get_subscription_history(history_id),
|
self._subscriptions.get_subscription_history(history_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_get_subscription_history(
|
async def async_get_subscription_history(
|
||||||
self,
|
self,
|
||||||
history_id: int,
|
history_id: int,
|
||||||
) -> SubscribeHistory | None:
|
) -> SubscriptionHistorySnapshot | None:
|
||||||
"""异步按 ID 查询订阅完成历史。"""
|
"""异步按 ID 查询订阅完成历史。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.get_subscription_history, history_id))
|
||||||
partial(self.get_subscription_history, history_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
def list_download_history(
|
def list_download_history(
|
||||||
self,
|
self,
|
||||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[DownloadHistory]:
|
) -> QueryPage[DownloadHistorySnapshot]:
|
||||||
"""同步分页查询下载历史。"""
|
"""同步分页查询下载历史。"""
|
||||||
normalized_page = self._page_request(page)
|
normalized_page = self._page_request(page)
|
||||||
normalized_filters = self._filter(filters, DownloadHistoryFilter)
|
normalized_filters = self._filter(filters, DownloadHistoryFilter)
|
||||||
@@ -261,36 +260,35 @@ class DataQueryService:
|
|||||||
filters=normalized_filters,
|
filters=normalized_filters,
|
||||||
page=normalized_page,
|
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(
|
async def async_list_download_history(
|
||||||
self,
|
self,
|
||||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[DownloadHistory]:
|
) -> QueryPage[DownloadHistorySnapshot]:
|
||||||
"""异步分页查询下载历史。"""
|
"""异步分页查询下载历史。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.list_download_history, filters, page))
|
||||||
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 查询下载历史。"""
|
"""同步按 ID 查询下载历史。"""
|
||||||
return self._to_item(
|
return self._to_item(
|
||||||
DownloadHistory,
|
DownloadHistorySnapshot,
|
||||||
self._histories.get_download_history(history_id),
|
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 查询下载历史。"""
|
"""异步按 ID 查询下载历史。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.get_download_history, history_id))
|
||||||
partial(self.get_download_history, history_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
def list_transfer_history(
|
def list_transfer_history(
|
||||||
self,
|
self,
|
||||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[TransferHistory]:
|
) -> QueryPage[TransferHistorySnapshot]:
|
||||||
"""同步分页查询整理历史。"""
|
"""同步分页查询整理历史。"""
|
||||||
normalized_page = self._page_request(page)
|
normalized_page = self._page_request(page)
|
||||||
normalized_filters = self._filter(filters, TransferHistoryFilter)
|
normalized_filters = self._filter(filters, TransferHistoryFilter)
|
||||||
@@ -298,30 +296,29 @@ class DataQueryService:
|
|||||||
filters=normalized_filters,
|
filters=normalized_filters,
|
||||||
page=normalized_page,
|
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(
|
async def async_list_transfer_history(
|
||||||
self,
|
self,
|
||||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[TransferHistory]:
|
) -> QueryPage[TransferHistorySnapshot]:
|
||||||
"""异步分页查询整理历史。"""
|
"""异步分页查询整理历史。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.list_transfer_history, filters, page))
|
||||||
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 查询整理历史。"""
|
"""同步按 ID 查询整理历史。"""
|
||||||
return self._to_item(
|
return self._to_item(
|
||||||
TransferHistory,
|
TransferHistorySnapshot,
|
||||||
self._histories.get_transfer_history(history_id),
|
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 查询整理历史。"""
|
"""异步按 ID 查询整理历史。"""
|
||||||
return await self._async_run(
|
return await self._async_run(partial(self.get_transfer_history, history_id))
|
||||||
partial(self.get_transfer_history, history_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_configured_data_query_service: DataQueryService | None = None
|
_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.subscribe import Subscribe
|
||||||
from app.db.models.subscribehistory import SubscribeHistory as SubscribeHistoryModel
|
from app.db.models.subscribehistory import SubscribeHistory as SubscribeHistoryModel
|
||||||
from app.db.models.transferhistory import TransferHistory
|
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 (
|
from app.schemas.query import (
|
||||||
DownloadHistoryFilter,
|
DownloadHistoryFilter,
|
||||||
|
DownloadHistorySnapshot,
|
||||||
QueryPageRequest,
|
QueryPageRequest,
|
||||||
QuerySortDirection,
|
QuerySortDirection,
|
||||||
QuerySortField,
|
QuerySortField,
|
||||||
SubscriptionFilter,
|
SubscriptionFilter,
|
||||||
SubscriptionHistoryFilter,
|
SubscriptionHistoryFilter,
|
||||||
|
SubscriptionHistorySnapshot,
|
||||||
|
SubscriptionSnapshot,
|
||||||
TransferHistoryFilter,
|
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
|
from app.schemas.types import MUSIC_ENTITY_RECORDING
|
||||||
|
|
||||||
_ModelT = TypeVar("_ModelT", bound=Base)
|
_ModelT = TypeVar("_ModelT", bound=Base)
|
||||||
@@ -168,7 +164,7 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
*,
|
*,
|
||||||
filters: SubscriptionFilter,
|
filters: SubscriptionFilter,
|
||||||
page: QueryPageRequest,
|
page: QueryPageRequest,
|
||||||
) -> QueryRows[SubscribeView]:
|
) -> QueryRows[SubscriptionSnapshot]:
|
||||||
"""按受控组合条件分页查询当前订阅。"""
|
"""按受控组合条件分页查询当前订阅。"""
|
||||||
query = filters
|
query = filters
|
||||||
conditions = self._identity_conditions(Subscribe, query)
|
conditions = self._identity_conditions(Subscribe, query)
|
||||||
@@ -196,16 +192,16 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
conditions.append(music_condition)
|
conditions.append(music_condition)
|
||||||
return self._page(
|
return self._page(
|
||||||
model=Subscribe,
|
model=Subscribe,
|
||||||
view_model=SubscribeView,
|
view_model=SubscriptionSnapshot,
|
||||||
conditions=conditions,
|
conditions=conditions,
|
||||||
page=page,
|
page=page,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_subscription(self, subscription_id: int) -> SubscribeView | None:
|
def get_subscription(self, subscription_id: int) -> SubscriptionSnapshot | None:
|
||||||
"""按主键查询订阅并返回脱离 Session 的 DTO。"""
|
"""按主键查询订阅并返回脱离 Session 的 DTO。"""
|
||||||
return self._get(
|
return self._get(
|
||||||
model=Subscribe,
|
model=Subscribe,
|
||||||
view_model=SubscribeView,
|
view_model=SubscriptionSnapshot,
|
||||||
record_id=subscription_id,
|
record_id=subscription_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -214,7 +210,7 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
*,
|
*,
|
||||||
filters: SubscriptionHistoryFilter,
|
filters: SubscriptionHistoryFilter,
|
||||||
page: QueryPageRequest,
|
page: QueryPageRequest,
|
||||||
) -> QueryRows[SubscribeHistoryView]:
|
) -> QueryRows[SubscriptionHistorySnapshot]:
|
||||||
"""按受控组合条件分页查询订阅完成历史。"""
|
"""按受控组合条件分页查询订阅完成历史。"""
|
||||||
query = filters
|
query = filters
|
||||||
conditions = self._identity_conditions(SubscribeHistoryModel, query)
|
conditions = self._identity_conditions(SubscribeHistoryModel, query)
|
||||||
@@ -242,7 +238,7 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
conditions.append(music_condition)
|
conditions.append(music_condition)
|
||||||
return self._page(
|
return self._page(
|
||||||
model=SubscribeHistoryModel,
|
model=SubscribeHistoryModel,
|
||||||
view_model=SubscribeHistoryView,
|
view_model=SubscriptionHistorySnapshot,
|
||||||
conditions=conditions,
|
conditions=conditions,
|
||||||
page=page,
|
page=page,
|
||||||
)
|
)
|
||||||
@@ -250,11 +246,11 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
def get_subscription_history(
|
def get_subscription_history(
|
||||||
self,
|
self,
|
||||||
history_id: int,
|
history_id: int,
|
||||||
) -> SubscribeHistoryView | None:
|
) -> SubscriptionHistorySnapshot | None:
|
||||||
"""按主键查询订阅完成历史并返回稳定 DTO。"""
|
"""按主键查询订阅完成历史并返回稳定 DTO。"""
|
||||||
return self._get(
|
return self._get(
|
||||||
model=SubscribeHistoryModel,
|
model=SubscribeHistoryModel,
|
||||||
view_model=SubscribeHistoryView,
|
view_model=SubscriptionHistorySnapshot,
|
||||||
record_id=history_id,
|
record_id=history_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -263,7 +259,7 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
*,
|
*,
|
||||||
filters: DownloadHistoryFilter,
|
filters: DownloadHistoryFilter,
|
||||||
page: QueryPageRequest,
|
page: QueryPageRequest,
|
||||||
) -> QueryRows[DownloadHistoryView]:
|
) -> QueryRows[DownloadHistorySnapshot]:
|
||||||
"""按受控组合条件分页查询下载历史。"""
|
"""按受控组合条件分页查询下载历史。"""
|
||||||
query = filters
|
query = filters
|
||||||
conditions = self._identity_conditions(DownloadHistory, query)
|
conditions = self._identity_conditions(DownloadHistory, query)
|
||||||
@@ -276,20 +272,23 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
conditions.append(DownloadHistory.type.in_(media_types))
|
conditions.append(DownloadHistory.type.in_(media_types))
|
||||||
for column, value in (
|
for column, value in (
|
||||||
(DownloadHistory.title, query.title),
|
(DownloadHistory.title, query.title),
|
||||||
(DownloadHistory.path, query.path),
|
|
||||||
):
|
|
||||||
if value:
|
|
||||||
conditions.append(_contains(column, value))
|
|
||||||
for column, value in (
|
|
||||||
(DownloadHistory.year, query.year),
|
(DownloadHistory.year, query.year),
|
||||||
(DownloadHistory.seasons, query.seasons),
|
(DownloadHistory.seasons, query.seasons),
|
||||||
(DownloadHistory.episodes, query.episodes),
|
(DownloadHistory.episodes, query.episodes),
|
||||||
|
(DownloadHistory.path, query.path),
|
||||||
(DownloadHistory.download_hash, query.download_hash),
|
(DownloadHistory.download_hash, query.download_hash),
|
||||||
(DownloadHistory.username, query.username),
|
(DownloadHistory.username, query.username),
|
||||||
(DownloadHistory.episode_group, query.episode_group),
|
(DownloadHistory.episode_group, query.episode_group),
|
||||||
):
|
):
|
||||||
if value is not None and value != "":
|
if value is not None and value != "":
|
||||||
conditions.append(column == value)
|
conditions.append(column == value)
|
||||||
|
if query.text:
|
||||||
|
conditions.append(
|
||||||
|
or_(
|
||||||
|
_contains(DownloadHistory.title, query.text),
|
||||||
|
_contains(DownloadHistory.path, query.text),
|
||||||
|
)
|
||||||
|
)
|
||||||
if usernames:
|
if usernames:
|
||||||
conditions.append(DownloadHistory.username.in_(usernames))
|
conditions.append(DownloadHistory.username.in_(usernames))
|
||||||
music_condition = _music_type_condition(DownloadHistory.music_type, query.music_type)
|
music_condition = _music_type_condition(DownloadHistory.music_type, query.music_type)
|
||||||
@@ -297,16 +296,16 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
conditions.append(music_condition)
|
conditions.append(music_condition)
|
||||||
return self._page(
|
return self._page(
|
||||||
model=DownloadHistory,
|
model=DownloadHistory,
|
||||||
view_model=DownloadHistoryView,
|
view_model=DownloadHistorySnapshot,
|
||||||
conditions=conditions,
|
conditions=conditions,
|
||||||
page=page,
|
page=page,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_download_history(self, history_id: int) -> DownloadHistoryView | None:
|
def get_download_history(self, history_id: int) -> DownloadHistorySnapshot | None:
|
||||||
"""按主键查询下载历史并返回稳定 DTO。"""
|
"""按主键查询下载历史并返回稳定 DTO。"""
|
||||||
return self._get(
|
return self._get(
|
||||||
model=DownloadHistory,
|
model=DownloadHistory,
|
||||||
view_model=DownloadHistoryView,
|
view_model=DownloadHistorySnapshot,
|
||||||
record_id=history_id,
|
record_id=history_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -315,7 +314,7 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
*,
|
*,
|
||||||
filters: TransferHistoryFilter,
|
filters: TransferHistoryFilter,
|
||||||
page: QueryPageRequest,
|
page: QueryPageRequest,
|
||||||
) -> QueryRows[TransferHistoryView]:
|
) -> QueryRows[TransferHistorySnapshot]:
|
||||||
"""按受控组合条件分页查询整理历史。"""
|
"""按受控组合条件分页查询整理历史。"""
|
||||||
query = filters
|
query = filters
|
||||||
conditions = self._identity_conditions(TransferHistory, query)
|
conditions = self._identity_conditions(TransferHistory, query)
|
||||||
@@ -331,7 +330,7 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
if query.require_media_identity:
|
if query.require_media_identity:
|
||||||
conditions.extend(self._require_media_identity(TransferHistory))
|
conditions.extend(self._require_media_identity(TransferHistory))
|
||||||
if query.title:
|
if query.title:
|
||||||
conditions.append(_contains(TransferHistory.title, query.title))
|
conditions.append(TransferHistory.title == query.title)
|
||||||
if query.text:
|
if query.text:
|
||||||
conditions.append(
|
conditions.append(
|
||||||
or_(
|
or_(
|
||||||
@@ -358,16 +357,16 @@ class SqlAlchemyDataQueryAdapter:
|
|||||||
conditions.append(music_condition)
|
conditions.append(music_condition)
|
||||||
return self._page(
|
return self._page(
|
||||||
model=TransferHistory,
|
model=TransferHistory,
|
||||||
view_model=TransferHistoryView,
|
view_model=TransferHistorySnapshot,
|
||||||
conditions=conditions,
|
conditions=conditions,
|
||||||
page=page,
|
page=page,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_transfer_history(self, history_id: int) -> TransferHistoryView | None:
|
def get_transfer_history(self, history_id: int) -> TransferHistorySnapshot | None:
|
||||||
"""按主键查询整理历史并返回稳定 DTO。"""
|
"""按主键查询整理历史并返回稳定 DTO。"""
|
||||||
return self._get(
|
return self._get(
|
||||||
model=TransferHistory,
|
model=TransferHistory,
|
||||||
view_model=TransferHistoryView,
|
view_model=TransferHistorySnapshot,
|
||||||
record_id=history_id,
|
record_id=history_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+164
-18
@@ -1,14 +1,12 @@
|
|||||||
"""插件只读数据查询使用的稳定筛选、分页与数据投影合同。"""
|
"""插件只读数据查询使用的稳定筛选、分页与数据投影合同。"""
|
||||||
|
|
||||||
from enum import Enum
|
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.common import JsonData
|
||||||
from app.schemas.history import DownloadHistory, TransferHistory
|
from app.schemas.media import OptionalMediaIdentityMixin, normalize_media_source
|
||||||
from app.schemas.media import OptionalMediaIdentityMixin
|
|
||||||
from app.schemas.subscribe import Subscribe
|
|
||||||
from app.schemas.types import MediaSource, MediaType
|
from app.schemas.types import MediaSource, MediaType
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
@@ -17,7 +15,7 @@ DEFAULT_QUERY_PAGE_SIZE = 50
|
|||||||
MAX_QUERY_PAGE_SIZE = 200
|
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")
|
model_config = ConfigDict(extra="forbid")
|
||||||
@@ -56,7 +54,7 @@ class QueryPageRequest(_QueryInput):
|
|||||||
sort: QuerySort = Field(default_factory=QuerySort)
|
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。"""
|
"""公开查询返回的分页 DTO。"""
|
||||||
|
|
||||||
items: list[T] = Field(default_factory=list)
|
items: list[T] = Field(default_factory=list)
|
||||||
@@ -74,23 +72,105 @@ class QueryPage(BaseModel, Generic[T]):
|
|||||||
return self.page * self.count < self.total
|
return self.page * self.count < self.total
|
||||||
|
|
||||||
|
|
||||||
class MediaIdentityQuery(OptionalMediaIdentityMixin, _QueryInput):
|
class MediaIdentityQuery(
|
||||||
|
OptionalMediaIdentityMixin,
|
||||||
|
_QueryInput,
|
||||||
|
):
|
||||||
"""允许省略身份,但显式筛选时要求来源与原生 ID 成对有效。"""
|
"""允许省略身份,但显式筛选时要求来源与原生 ID 成对有效。"""
|
||||||
|
|
||||||
media_source: Optional[MediaSource] = None
|
media_source: Optional[MediaSource] = None
|
||||||
media_id: Optional[str] = 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
|
id: int
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
year: Optional[str] = None
|
year: Optional[str] = None
|
||||||
type: Optional[str] = None
|
type: Optional[str] = None
|
||||||
keyword: Optional[str] = None
|
keyword: Optional[str] = None
|
||||||
media_source: Optional[MediaSource] = None
|
|
||||||
media_id: Optional[str] = None
|
|
||||||
music_type: Optional[str] = None
|
music_type: Optional[str] = None
|
||||||
total_tracks: Optional[int] = None
|
total_tracks: Optional[int] = None
|
||||||
season: Optional[int] = None
|
season: Optional[int] = None
|
||||||
@@ -124,13 +204,78 @@ class SubscribeHistory(OptionalMediaIdentityMixin, BaseModel):
|
|||||||
episode_priority: Optional[dict[str, int]] = None
|
episode_priority: Optional[dict[str, int]] = None
|
||||||
save_path: Optional[str] = None
|
save_path: Optional[str] = None
|
||||||
search_imdbid: Optional[int] = None
|
search_imdbid: Optional[int] = None
|
||||||
note: Optional[JsonData] = None
|
|
||||||
custom_words: Optional[str] = None
|
custom_words: Optional[str] = None
|
||||||
media_category: Optional[str] = None
|
media_category: Optional[str] = None
|
||||||
filter_groups: Optional[list[str]] = None
|
filter_groups: Optional[list[str]] = None
|
||||||
episode_group: Optional[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):
|
class SubscriptionFilter(MediaIdentityQuery):
|
||||||
@@ -164,6 +309,7 @@ class DownloadHistoryFilter(MediaIdentityQuery):
|
|||||||
ids: tuple[int, ...] = ()
|
ids: tuple[int, ...] = ()
|
||||||
media_types: tuple[MediaType, ...] = ()
|
media_types: tuple[MediaType, ...] = ()
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
|
text: Optional[str] = None
|
||||||
year: Optional[str] = None
|
year: Optional[str] = None
|
||||||
seasons: Optional[str] = None
|
seasons: Optional[str] = None
|
||||||
episodes: Optional[str] = None
|
episodes: Optional[str] = None
|
||||||
@@ -198,18 +344,18 @@ class TransferHistoryFilter(MediaIdentityQuery):
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"DEFAULT_QUERY_PAGE_SIZE",
|
"DEFAULT_QUERY_PAGE_SIZE",
|
||||||
"MAX_QUERY_PAGE_SIZE",
|
"MAX_QUERY_PAGE_SIZE",
|
||||||
"DownloadHistory",
|
|
||||||
"DownloadHistoryFilter",
|
"DownloadHistoryFilter",
|
||||||
|
"DownloadHistorySnapshot",
|
||||||
"MediaIdentityQuery",
|
"MediaIdentityQuery",
|
||||||
"QueryPage",
|
"QueryPage",
|
||||||
"QueryPageRequest",
|
"QueryPageRequest",
|
||||||
"QuerySort",
|
"QuerySort",
|
||||||
"QuerySortDirection",
|
"QuerySortDirection",
|
||||||
"QuerySortField",
|
"QuerySortField",
|
||||||
"Subscribe",
|
|
||||||
"SubscribeHistory",
|
|
||||||
"SubscriptionFilter",
|
"SubscriptionFilter",
|
||||||
|
"SubscriptionHistorySnapshot",
|
||||||
"SubscriptionHistoryFilter",
|
"SubscriptionHistoryFilter",
|
||||||
"TransferHistory",
|
"SubscriptionSnapshot",
|
||||||
"TransferHistoryFilter",
|
"TransferHistoryFilter",
|
||||||
|
"TransferHistorySnapshot",
|
||||||
]
|
]
|
||||||
|
|||||||
+127
-27
@@ -2,41 +2,133 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, Protocol, cast
|
||||||
|
|
||||||
from app.schemas.query import (
|
from app.schemas.query import (
|
||||||
DEFAULT_QUERY_PAGE_SIZE,
|
DEFAULT_QUERY_PAGE_SIZE,
|
||||||
MAX_QUERY_PAGE_SIZE,
|
MAX_QUERY_PAGE_SIZE,
|
||||||
DownloadHistory,
|
|
||||||
DownloadHistoryFilter,
|
DownloadHistoryFilter,
|
||||||
|
DownloadHistorySnapshot,
|
||||||
MediaIdentityQuery,
|
MediaIdentityQuery,
|
||||||
QueryPage,
|
QueryPage,
|
||||||
QueryPageRequest,
|
QueryPageRequest,
|
||||||
QuerySort,
|
QuerySort,
|
||||||
QuerySortDirection,
|
QuerySortDirection,
|
||||||
QuerySortField,
|
QuerySortField,
|
||||||
Subscribe,
|
|
||||||
SubscribeHistory,
|
|
||||||
SubscriptionFilter,
|
SubscriptionFilter,
|
||||||
SubscriptionHistoryFilter,
|
SubscriptionHistoryFilter,
|
||||||
TransferHistory,
|
SubscriptionHistorySnapshot,
|
||||||
|
SubscriptionSnapshot,
|
||||||
TransferHistoryFilter,
|
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 合同。"""
|
"""获取启动阶段登记的查询服务,避免把应用服务暴露为 SDK 合同。"""
|
||||||
from app.application.data_query import (
|
from app.application.data_query import (
|
||||||
get_configured_data_query_service,
|
get_configured_data_query_service,
|
||||||
)
|
)
|
||||||
|
|
||||||
return get_configured_data_query_service()
|
return cast(_DataQueryBackend, get_configured_data_query_service())
|
||||||
|
|
||||||
|
|
||||||
def list_subscriptions(
|
def list_subscriptions(
|
||||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[Subscribe]:
|
) -> QueryPage[SubscriptionSnapshot]:
|
||||||
"""同步分页读取订阅 DTO。"""
|
"""同步分页读取订阅 DTO。"""
|
||||||
return _service().list_subscriptions(filters, page)
|
return _service().list_subscriptions(filters, page)
|
||||||
|
|
||||||
@@ -44,17 +136,19 @@ def list_subscriptions(
|
|||||||
async def async_list_subscriptions(
|
async def async_list_subscriptions(
|
||||||
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
filters: SubscriptionFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[Subscribe]:
|
) -> QueryPage[SubscriptionSnapshot]:
|
||||||
"""异步分页读取订阅 DTO。"""
|
"""异步分页读取订阅 DTO。"""
|
||||||
return await _service().async_list_subscriptions(filters, page)
|
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。"""
|
"""同步按 ID 读取订阅 DTO。"""
|
||||||
return _service().get_subscription(subscription_id)
|
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。"""
|
"""异步按 ID 读取订阅 DTO。"""
|
||||||
return await _service().async_get_subscription(subscription_id)
|
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(
|
def list_subscription_history(
|
||||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[SubscribeHistory]:
|
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||||
"""同步分页读取订阅完成历史 DTO。"""
|
"""同步分页读取订阅完成历史 DTO。"""
|
||||||
return _service().list_subscription_history(filters, page)
|
return _service().list_subscription_history(filters, page)
|
||||||
|
|
||||||
@@ -70,17 +164,19 @@ def list_subscription_history(
|
|||||||
async def async_list_subscription_history(
|
async def async_list_subscription_history(
|
||||||
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
filters: SubscriptionHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[SubscribeHistory]:
|
) -> QueryPage[SubscriptionHistorySnapshot]:
|
||||||
"""异步分页读取订阅完成历史 DTO。"""
|
"""异步分页读取订阅完成历史 DTO。"""
|
||||||
return await _service().async_list_subscription_history(filters, page)
|
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。"""
|
"""同步按 ID 读取订阅完成历史 DTO。"""
|
||||||
return _service().get_subscription_history(history_id)
|
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。"""
|
"""异步按 ID 读取订阅完成历史 DTO。"""
|
||||||
return await _service().async_get_subscription_history(history_id)
|
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(
|
def list_download_history(
|
||||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[DownloadHistory]:
|
) -> QueryPage[DownloadHistorySnapshot]:
|
||||||
"""同步分页读取下载历史 DTO。"""
|
"""同步分页读取下载历史 DTO。"""
|
||||||
return _service().list_download_history(filters, page)
|
return _service().list_download_history(filters, page)
|
||||||
|
|
||||||
@@ -96,17 +192,19 @@ def list_download_history(
|
|||||||
async def async_list_download_history(
|
async def async_list_download_history(
|
||||||
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
filters: DownloadHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[DownloadHistory]:
|
) -> QueryPage[DownloadHistorySnapshot]:
|
||||||
"""异步分页读取下载历史 DTO。"""
|
"""异步分页读取下载历史 DTO。"""
|
||||||
return await _service().async_list_download_history(filters, page)
|
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。"""
|
"""同步按 ID 读取下载历史 DTO。"""
|
||||||
return _service().get_download_history(history_id)
|
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。"""
|
"""异步按 ID 读取下载历史 DTO。"""
|
||||||
return await _service().async_get_download_history(history_id)
|
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(
|
def list_transfer_history(
|
||||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[TransferHistory]:
|
) -> QueryPage[TransferHistorySnapshot]:
|
||||||
"""同步分页读取整理历史 DTO。"""
|
"""同步分页读取整理历史 DTO。"""
|
||||||
return _service().list_transfer_history(filters, page)
|
return _service().list_transfer_history(filters, page)
|
||||||
|
|
||||||
@@ -122,17 +220,19 @@ def list_transfer_history(
|
|||||||
async def async_list_transfer_history(
|
async def async_list_transfer_history(
|
||||||
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
filters: TransferHistoryFilter | dict[str, Any] | None = None,
|
||||||
page: QueryPageRequest | dict[str, Any] | None = None,
|
page: QueryPageRequest | dict[str, Any] | None = None,
|
||||||
) -> QueryPage[TransferHistory]:
|
) -> QueryPage[TransferHistorySnapshot]:
|
||||||
"""异步分页读取整理历史 DTO。"""
|
"""异步分页读取整理历史 DTO。"""
|
||||||
return await _service().async_list_transfer_history(filters, page)
|
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。"""
|
"""同步按 ID 读取整理历史 DTO。"""
|
||||||
return _service().get_transfer_history(history_id)
|
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。"""
|
"""异步按 ID 读取整理历史 DTO。"""
|
||||||
return await _service().async_get_transfer_history(history_id)
|
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__ = [
|
__all__ = [
|
||||||
"DEFAULT_QUERY_PAGE_SIZE",
|
"DEFAULT_QUERY_PAGE_SIZE",
|
||||||
"MAX_QUERY_PAGE_SIZE",
|
"MAX_QUERY_PAGE_SIZE",
|
||||||
"DownloadHistory",
|
|
||||||
"DownloadHistoryFilter",
|
"DownloadHistoryFilter",
|
||||||
|
"DownloadHistorySnapshot",
|
||||||
"MediaIdentityQuery",
|
"MediaIdentityQuery",
|
||||||
"QueryPage",
|
"QueryPage",
|
||||||
"QueryPageRequest",
|
"QueryPageRequest",
|
||||||
"QuerySort",
|
"QuerySort",
|
||||||
"QuerySortDirection",
|
"QuerySortDirection",
|
||||||
"QuerySortField",
|
"QuerySortField",
|
||||||
"Subscribe",
|
|
||||||
"SubscribeHistory",
|
|
||||||
"SubscriptionFilter",
|
"SubscriptionFilter",
|
||||||
"SubscriptionHistoryFilter",
|
"SubscriptionHistoryFilter",
|
||||||
"TransferHistory",
|
"SubscriptionHistorySnapshot",
|
||||||
|
"SubscriptionSnapshot",
|
||||||
"TransferHistoryFilter",
|
"TransferHistoryFilter",
|
||||||
|
"TransferHistorySnapshot",
|
||||||
"async_get_download_history",
|
"async_get_download_history",
|
||||||
"async_get_subscription",
|
"async_get_subscription",
|
||||||
"async_get_subscription_history",
|
"async_get_subscription_history",
|
||||||
|
|||||||
+33
-3
@@ -1441,8 +1441,8 @@
|
|||||||
"runtime_only": true
|
"runtime_only": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"edge_count": 6898,
|
"edge_count": 6924,
|
||||||
"edge_sha256": "c73faf7e29ee12a862cf2332f1803fd4eb8a3f4d2994ed81075ff198e896f8df",
|
"edge_sha256": "d5471439086bcfdd8060b77670af6f821a34e3a864ba7e2cb7230c599a1cb3bb",
|
||||||
"edges": [
|
"edges": [
|
||||||
"app -> app.runtime",
|
"app -> app.runtime",
|
||||||
"app -> app.runtime.compat",
|
"app -> app.runtime.compat",
|
||||||
@@ -4008,6 +4008,10 @@
|
|||||||
"app.application.configuration -> app.schemas.types",
|
"app.application.configuration -> app.schemas.types",
|
||||||
"app.application.dashboard -> app.schemas",
|
"app.application.dashboard -> app.schemas",
|
||||||
"app.application.dashboard -> app.schemas.dashboard",
|
"app.application.dashboard -> app.schemas.dashboard",
|
||||||
|
"app.application.data_query -> app.application",
|
||||||
|
"app.application.data_query -> app.application.database",
|
||||||
|
"app.application.data_query -> app.schemas",
|
||||||
|
"app.application.data_query -> app.schemas.query",
|
||||||
"app.application.directory -> app.adapters",
|
"app.application.directory -> app.adapters",
|
||||||
"app.application.directory -> app.adapters.system",
|
"app.application.directory -> app.adapters.system",
|
||||||
"app.application.directory -> app.adapters.system.host",
|
"app.application.directory -> app.adapters.system.host",
|
||||||
@@ -5131,6 +5135,18 @@
|
|||||||
"app.db.adapters.chain -> app.db.oper.transferpending",
|
"app.db.adapters.chain -> app.db.oper.transferpending",
|
||||||
"app.db.adapters.chain -> app.db.oper.transfersettlementreceipt",
|
"app.db.adapters.chain -> app.db.oper.transfersettlementreceipt",
|
||||||
"app.db.adapters.chain -> app.db.uow",
|
"app.db.adapters.chain -> app.db.uow",
|
||||||
|
"app.db.adapters.data_query -> app.application",
|
||||||
|
"app.db.adapters.data_query -> app.application.data_query",
|
||||||
|
"app.db.adapters.data_query -> app.db",
|
||||||
|
"app.db.adapters.data_query -> app.db.base",
|
||||||
|
"app.db.adapters.data_query -> app.db.models",
|
||||||
|
"app.db.adapters.data_query -> app.db.models.downloadhistory",
|
||||||
|
"app.db.adapters.data_query -> app.db.models.subscribe",
|
||||||
|
"app.db.adapters.data_query -> app.db.models.subscribehistory",
|
||||||
|
"app.db.adapters.data_query -> app.db.models.transferhistory",
|
||||||
|
"app.db.adapters.data_query -> app.schemas",
|
||||||
|
"app.db.adapters.data_query -> app.schemas.query",
|
||||||
|
"app.db.adapters.data_query -> app.schemas.types",
|
||||||
"app.db.adapters.download -> app.db",
|
"app.db.adapters.download -> app.db",
|
||||||
"app.db.adapters.download -> app.db.oper",
|
"app.db.adapters.download -> app.db.oper",
|
||||||
"app.db.adapters.download -> app.db.oper.downloadfailure",
|
"app.db.adapters.download -> app.db.oper.downloadfailure",
|
||||||
@@ -7629,6 +7645,10 @@
|
|||||||
"app.schemas.openai -> app.schemas.common",
|
"app.schemas.openai -> app.schemas.common",
|
||||||
"app.schemas.plugin -> app.schemas",
|
"app.schemas.plugin -> app.schemas",
|
||||||
"app.schemas.plugin -> app.schemas.common",
|
"app.schemas.plugin -> app.schemas.common",
|
||||||
|
"app.schemas.query -> app.schemas",
|
||||||
|
"app.schemas.query -> app.schemas.common",
|
||||||
|
"app.schemas.query -> app.schemas.media",
|
||||||
|
"app.schemas.query -> app.schemas.types",
|
||||||
"app.schemas.response -> app.runtime",
|
"app.schemas.response -> app.runtime",
|
||||||
"app.schemas.response -> app.runtime.localization",
|
"app.schemas.response -> app.runtime.localization",
|
||||||
"app.schemas.search -> app.schemas",
|
"app.schemas.search -> app.schemas",
|
||||||
@@ -7748,6 +7768,10 @@
|
|||||||
"app.sdk.plugins -> app.runtime.extensions",
|
"app.sdk.plugins -> app.runtime.extensions",
|
||||||
"app.sdk.plugins -> app.runtime.extensions.module_manager",
|
"app.sdk.plugins -> app.runtime.extensions.module_manager",
|
||||||
"app.sdk.plugins -> app.runtime.extensions.plugin_manager",
|
"app.sdk.plugins -> app.runtime.extensions.plugin_manager",
|
||||||
|
"app.sdk.queries -> app.application",
|
||||||
|
"app.sdk.queries -> app.application.data_query",
|
||||||
|
"app.sdk.queries -> app.schemas",
|
||||||
|
"app.sdk.queries -> app.schemas.query",
|
||||||
"app.sdk.security -> app.adapters",
|
"app.sdk.security -> app.adapters",
|
||||||
"app.sdk.security -> app.adapters.web",
|
"app.sdk.security -> app.adapters.web",
|
||||||
"app.sdk.security -> app.adapters.web.security",
|
"app.sdk.security -> app.adapters.web.security",
|
||||||
@@ -7929,6 +7953,7 @@
|
|||||||
"app.startup.initializers.modules -> app.application.chain.data",
|
"app.startup.initializers.modules -> app.application.chain.data",
|
||||||
"app.startup.initializers.modules -> app.application.chain.events",
|
"app.startup.initializers.modules -> app.application.chain.events",
|
||||||
"app.startup.initializers.modules -> app.application.configuration",
|
"app.startup.initializers.modules -> app.application.configuration",
|
||||||
|
"app.startup.initializers.modules -> app.application.data_query",
|
||||||
"app.startup.initializers.modules -> app.application.database",
|
"app.startup.initializers.modules -> app.application.database",
|
||||||
"app.startup.initializers.modules -> app.application.history",
|
"app.startup.initializers.modules -> app.application.history",
|
||||||
"app.startup.initializers.modules -> app.application.image",
|
"app.startup.initializers.modules -> app.application.image",
|
||||||
@@ -7971,6 +7996,7 @@
|
|||||||
"app.startup.initializers.modules -> app.db",
|
"app.startup.initializers.modules -> app.db",
|
||||||
"app.startup.initializers.modules -> app.db.adapters",
|
"app.startup.initializers.modules -> app.db.adapters",
|
||||||
"app.startup.initializers.modules -> app.db.adapters.chain",
|
"app.startup.initializers.modules -> app.db.adapters.chain",
|
||||||
|
"app.startup.initializers.modules -> app.db.adapters.data_query",
|
||||||
"app.startup.initializers.modules -> app.db.adapters.download",
|
"app.startup.initializers.modules -> app.db.adapters.download",
|
||||||
"app.startup.initializers.modules -> app.db.adapters.outbox",
|
"app.startup.initializers.modules -> app.db.adapters.outbox",
|
||||||
"app.startup.initializers.modules -> app.db.adapters.pluginidentity",
|
"app.startup.initializers.modules -> app.db.adapters.pluginidentity",
|
||||||
@@ -8343,7 +8369,7 @@
|
|||||||
"app.workflow.actions.transfer_file -> app.workflow",
|
"app.workflow.actions.transfer_file -> app.workflow",
|
||||||
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
"app.workflow.actions.transfer_file -> app.workflow.actions"
|
||||||
],
|
],
|
||||||
"module_count": 844,
|
"module_count": 848,
|
||||||
"modules": [
|
"modules": [
|
||||||
"app",
|
"app",
|
||||||
"app.adapters",
|
"app.adapters",
|
||||||
@@ -8603,6 +8629,7 @@
|
|||||||
"app.application.commands",
|
"app.application.commands",
|
||||||
"app.application.configuration",
|
"app.application.configuration",
|
||||||
"app.application.dashboard",
|
"app.application.dashboard",
|
||||||
|
"app.application.data_query",
|
||||||
"app.application.database",
|
"app.application.database",
|
||||||
"app.application.directory",
|
"app.application.directory",
|
||||||
"app.application.download",
|
"app.application.download",
|
||||||
@@ -8735,6 +8762,7 @@
|
|||||||
"app.db",
|
"app.db",
|
||||||
"app.db.adapters",
|
"app.db.adapters",
|
||||||
"app.db.adapters.chain",
|
"app.db.adapters.chain",
|
||||||
|
"app.db.adapters.data_query",
|
||||||
"app.db.adapters.download",
|
"app.db.adapters.download",
|
||||||
"app.db.adapters.outbox",
|
"app.db.adapters.outbox",
|
||||||
"app.db.adapters.pluginidentity",
|
"app.db.adapters.pluginidentity",
|
||||||
@@ -9111,6 +9139,7 @@
|
|||||||
"app.schemas.notification",
|
"app.schemas.notification",
|
||||||
"app.schemas.openai",
|
"app.schemas.openai",
|
||||||
"app.schemas.plugin",
|
"app.schemas.plugin",
|
||||||
|
"app.schemas.query",
|
||||||
"app.schemas.response",
|
"app.schemas.response",
|
||||||
"app.schemas.rule",
|
"app.schemas.rule",
|
||||||
"app.schemas.search",
|
"app.schemas.search",
|
||||||
@@ -9142,6 +9171,7 @@
|
|||||||
"app.sdk.media",
|
"app.sdk.media",
|
||||||
"app.sdk.network",
|
"app.sdk.network",
|
||||||
"app.sdk.plugins",
|
"app.sdk.plugins",
|
||||||
|
"app.sdk.queries",
|
||||||
"app.sdk.security",
|
"app.sdk.security",
|
||||||
"app.sdk.services",
|
"app.sdk.services",
|
||||||
"app.sdk.string",
|
"app.sdk.string",
|
||||||
|
|||||||
@@ -10174,6 +10174,168 @@
|
|||||||
"target": "app.runtime.extensions.plugin_manager.PluginManager"
|
"target": "app.runtime.extensions.plugin_manager.PluginManager"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"app.sdk.queries": [
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "DEFAULT_QUERY_PAGE_SIZE",
|
||||||
|
"target": "app.schemas.query.DEFAULT_QUERY_PAGE_SIZE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "DownloadHistoryFilter",
|
||||||
|
"target": "app.schemas.query.DownloadHistoryFilter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "DownloadHistorySnapshot",
|
||||||
|
"target": "app.schemas.query.DownloadHistorySnapshot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "MAX_QUERY_PAGE_SIZE",
|
||||||
|
"target": "app.schemas.query.MAX_QUERY_PAGE_SIZE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "MediaIdentityQuery",
|
||||||
|
"target": "app.schemas.query.MediaIdentityQuery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "QueryPage",
|
||||||
|
"target": "app.schemas.query.QueryPage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "QueryPageRequest",
|
||||||
|
"target": "app.schemas.query.QueryPageRequest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "QuerySort",
|
||||||
|
"target": "app.schemas.query.QuerySort"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "QuerySortDirection",
|
||||||
|
"target": "app.schemas.query.QuerySortDirection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "QuerySortField",
|
||||||
|
"target": "app.schemas.query.QuerySortField"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "SubscriptionFilter",
|
||||||
|
"target": "app.schemas.query.SubscriptionFilter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "SubscriptionHistoryFilter",
|
||||||
|
"target": "app.schemas.query.SubscriptionHistoryFilter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "SubscriptionHistorySnapshot",
|
||||||
|
"target": "app.schemas.query.SubscriptionHistorySnapshot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "SubscriptionSnapshot",
|
||||||
|
"target": "app.schemas.query.SubscriptionSnapshot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "TransferHistoryFilter",
|
||||||
|
"target": "app.schemas.query.TransferHistoryFilter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "import",
|
||||||
|
"name": "TransferHistorySnapshot",
|
||||||
|
"target": "app.schemas.query.TransferHistorySnapshot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_get_download_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_get_subscription",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_get_subscription_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_get_transfer_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_list_download_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_list_subscription_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_list_subscriptions",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "AsyncFunctionDef",
|
||||||
|
"name": "async_list_transfer_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "get_download_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "get_subscription",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "get_subscription_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "get_transfer_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "list_download_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "list_subscription_history",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "list_subscriptions",
|
||||||
|
"target": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "FunctionDef",
|
||||||
|
"name": "list_transfer_history",
|
||||||
|
"target": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
"app.sdk.security": [
|
"app.sdk.security": [
|
||||||
{
|
{
|
||||||
"kind": "import",
|
"kind": "import",
|
||||||
|
|||||||
+120
-37
@@ -11,6 +11,7 @@ import asyncio
|
|||||||
import inspect
|
import inspect
|
||||||
import threading
|
import threading
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from types import SimpleNamespace
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -19,26 +20,23 @@ from app.db.models.downloadhistory import DownloadHistory as DownloadHistoryMode
|
|||||||
from app.db.models.subscribe import Subscribe as SubscribeModel
|
from app.db.models.subscribe import Subscribe as SubscribeModel
|
||||||
from app.db.models.subscribehistory import SubscribeHistory as SubscribeHistoryModel
|
from app.db.models.subscribehistory import SubscribeHistory as SubscribeHistoryModel
|
||||||
from app.db.models.transferhistory import TransferHistory as TransferHistoryModel
|
from app.db.models.transferhistory import TransferHistory as TransferHistoryModel
|
||||||
from app.schemas.history import (
|
|
||||||
DownloadHistory as DownloadHistoryDTO,
|
|
||||||
TransferHistory as TransferHistoryDTO,
|
|
||||||
)
|
|
||||||
from app.schemas.query import (
|
from app.schemas.query import (
|
||||||
DownloadHistoryFilter,
|
DownloadHistoryFilter,
|
||||||
|
DownloadHistorySnapshot,
|
||||||
QueryPage,
|
QueryPage,
|
||||||
QueryPageRequest,
|
QueryPageRequest,
|
||||||
QuerySort,
|
QuerySort,
|
||||||
QuerySortDirection,
|
QuerySortDirection,
|
||||||
QuerySortField,
|
QuerySortField,
|
||||||
SubscribeHistory as SubscribeHistoryDTO,
|
|
||||||
SubscriptionFilter,
|
SubscriptionFilter,
|
||||||
SubscriptionHistoryFilter,
|
SubscriptionHistoryFilter,
|
||||||
|
SubscriptionHistorySnapshot,
|
||||||
|
SubscriptionSnapshot,
|
||||||
TransferHistoryFilter,
|
TransferHistoryFilter,
|
||||||
|
TransferHistorySnapshot,
|
||||||
)
|
)
|
||||||
from app.schemas.subscribe import Subscribe as SubscribeDTO
|
|
||||||
from app.schemas.types import MediaSource, MediaType
|
from app.schemas.types import MediaSource, MediaType
|
||||||
|
|
||||||
|
|
||||||
TMDB = MediaSource.TMDB.value
|
TMDB = MediaSource.TMDB.value
|
||||||
|
|
||||||
|
|
||||||
@@ -95,6 +93,7 @@ def _subscribe(
|
|||||||
episode_group: str | None = None,
|
episode_group: str | None = None,
|
||||||
date: str = "2026-08-27 10:00:00",
|
date: str = "2026-08-27 10:00:00",
|
||||||
music_type: str | None = None,
|
music_type: str | None = None,
|
||||||
|
manual_total_episode: int | None = 0,
|
||||||
) -> SubscribeModel:
|
) -> SubscribeModel:
|
||||||
"""构造隔离用例使用的订阅行。"""
|
"""构造隔离用例使用的订阅行。"""
|
||||||
return SubscribeModel(
|
return SubscribeModel(
|
||||||
@@ -108,6 +107,7 @@ def _subscribe(
|
|||||||
username=username,
|
username=username,
|
||||||
date=date,
|
date=date,
|
||||||
music_type=music_type,
|
music_type=music_type,
|
||||||
|
manual_total_episode=manual_total_episode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -218,13 +218,15 @@ def _assert_projected_page(page: QueryPage, dto_type: type[Any], model_type: typ
|
|||||||
def test_sdk_projects_subscription_and_three_history_domains_to_dtos(db, query_sdk):
|
def test_sdk_projects_subscription_and_three_history_domains_to_dtos(db, query_sdk):
|
||||||
"""订阅、订阅完成历史、下载历史、整理历史均只返回 Pydantic 投影。"""
|
"""订阅、订阅完成历史、下载历史、整理历史均只返回 Pydantic 投影。"""
|
||||||
sdk, _executor = query_sdk
|
sdk, _executor = query_sdk
|
||||||
subscribe = db.add(_subscribe("订阅 DTO", media_id="dto-sub"))
|
subscribe = db.add(
|
||||||
subscribe_history = db.add(
|
_subscribe(
|
||||||
_subscribe_history("订阅历史 DTO", media_id="dto-sub-history")
|
"订阅 DTO",
|
||||||
|
media_id="dto-sub",
|
||||||
|
manual_total_episode=1,
|
||||||
)
|
)
|
||||||
download_history = db.add(
|
|
||||||
_download_history("下载历史 DTO", media_id="dto-download", path="/dto/download")
|
|
||||||
)
|
)
|
||||||
|
subscribe_history = db.add(_subscribe_history("订阅历史 DTO", media_id="dto-sub-history"))
|
||||||
|
download_history = db.add(_download_history("下载历史 DTO", media_id="dto-download", path="/dto/download"))
|
||||||
transfer_history = db.add(
|
transfer_history = db.add(
|
||||||
_transfer_history(
|
_transfer_history(
|
||||||
"整理历史 DTO",
|
"整理历史 DTO",
|
||||||
@@ -235,13 +237,12 @@ def test_sdk_projects_subscription_and_three_history_domains_to_dtos(db, query_s
|
|||||||
)
|
)
|
||||||
|
|
||||||
_assert_projected_page(
|
_assert_projected_page(
|
||||||
sdk.list_subscriptions(
|
subscription_page := sdk.list_subscriptions(SubscriptionFilter(media_source=TMDB, media_id="dto-sub")),
|
||||||
SubscriptionFilter(media_source=TMDB, media_id="dto-sub")
|
SubscriptionSnapshot,
|
||||||
),
|
|
||||||
SubscribeDTO,
|
|
||||||
SubscribeModel,
|
SubscribeModel,
|
||||||
subscribe.id,
|
subscribe.id,
|
||||||
)
|
)
|
||||||
|
assert subscription_page.items[0].manual_total_episode == 1
|
||||||
_assert_projected_page(
|
_assert_projected_page(
|
||||||
sdk.list_subscription_history(
|
sdk.list_subscription_history(
|
||||||
SubscriptionHistoryFilter(
|
SubscriptionHistoryFilter(
|
||||||
@@ -249,23 +250,19 @@ def test_sdk_projects_subscription_and_three_history_domains_to_dtos(db, query_s
|
|||||||
media_id="dto-sub-history",
|
media_id="dto-sub-history",
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
SubscribeHistoryDTO,
|
SubscriptionHistorySnapshot,
|
||||||
SubscribeHistoryModel,
|
SubscribeHistoryModel,
|
||||||
subscribe_history.id,
|
subscribe_history.id,
|
||||||
)
|
)
|
||||||
_assert_projected_page(
|
_assert_projected_page(
|
||||||
sdk.list_download_history(
|
sdk.list_download_history(DownloadHistoryFilter(media_source=TMDB, media_id="dto-download")),
|
||||||
DownloadHistoryFilter(media_source=TMDB, media_id="dto-download")
|
DownloadHistorySnapshot,
|
||||||
),
|
|
||||||
DownloadHistoryDTO,
|
|
||||||
DownloadHistoryModel,
|
DownloadHistoryModel,
|
||||||
download_history.id,
|
download_history.id,
|
||||||
)
|
)
|
||||||
_assert_projected_page(
|
_assert_projected_page(
|
||||||
sdk.list_transfer_history(
|
sdk.list_transfer_history(TransferHistoryFilter(media_source=TMDB, media_id="dto-transfer")),
|
||||||
TransferHistoryFilter(media_source=TMDB, media_id="dto-transfer")
|
TransferHistorySnapshot,
|
||||||
),
|
|
||||||
TransferHistoryDTO,
|
|
||||||
TransferHistoryModel,
|
TransferHistoryModel,
|
||||||
transfer_history.id,
|
transfer_history.id,
|
||||||
)
|
)
|
||||||
@@ -422,11 +419,12 @@ def test_sdk_applies_combined_filters_in_each_query_domain(db, query_sdk):
|
|||||||
media_source=TMDB,
|
media_source=TMDB,
|
||||||
media_id="combo-download",
|
media_id="combo-download",
|
||||||
media_types=(MediaType.TV,),
|
media_types=(MediaType.TV,),
|
||||||
title="Combo",
|
title="Combo Film",
|
||||||
|
text="match",
|
||||||
year="2026",
|
year="2026",
|
||||||
seasons="S02",
|
seasons="S02",
|
||||||
episodes="E03",
|
episodes="E03",
|
||||||
path="match",
|
path="/combo/match.mkv",
|
||||||
download_hash="combo-hash",
|
download_hash="combo-hash",
|
||||||
username="alice",
|
username="alice",
|
||||||
episode_group="eg-a",
|
episode_group="eg-a",
|
||||||
@@ -475,7 +473,7 @@ def test_sdk_applies_combined_filters_in_each_query_domain(db, query_sdk):
|
|||||||
media_types=(MediaType.TV,),
|
media_types=(MediaType.TV,),
|
||||||
media_sources=(MediaSource.TMDB,),
|
media_sources=(MediaSource.TMDB,),
|
||||||
require_media_identity=True,
|
require_media_identity=True,
|
||||||
title="Transfer",
|
title="Transfer Combo",
|
||||||
text="dest-match",
|
text="dest-match",
|
||||||
year="2026",
|
year="2026",
|
||||||
seasons="S02",
|
seasons="S02",
|
||||||
@@ -533,9 +531,7 @@ def test_sdk_pagination_reports_total_and_stable_date_id_order(db, query_sdk):
|
|||||||
assert page_one.page == 1
|
assert page_one.page == 1
|
||||||
assert page_one.count == 2
|
assert page_one.count == 2
|
||||||
assert page_one.has_next is True
|
assert page_one.has_next is True
|
||||||
assert [item.id for item in page_one.items] == [
|
assert [item.id for item in page_one.items] == [row.id for row in expected_desc[:2]]
|
||||||
row.id for row in expected_desc[:2]
|
|
||||||
]
|
|
||||||
assert page_two.total == 3
|
assert page_two.total == 3
|
||||||
assert page_two.has_next is False
|
assert page_two.has_next is False
|
||||||
assert [item.id for item in page_two.items] == [expected_desc[2].id]
|
assert [item.id for item in page_two.items] == [expected_desc[2].id]
|
||||||
@@ -552,9 +548,98 @@ def test_sdk_pagination_reports_total_and_stable_date_id_order(db, query_sdk):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
expected_asc = sorted(rows, key=lambda row: (row.date, row.id))
|
expected_asc = sorted(rows, key=lambda row: (row.date, row.id))
|
||||||
assert [item.id for item in asc_page.items] == [
|
assert [item.id for item in asc_page.items] == [row.id for row in expected_asc]
|
||||||
row.id for row in expected_asc
|
|
||||||
]
|
|
||||||
|
def test_structured_text_fields_are_exact_and_text_search_is_explicit(db, query_sdk):
|
||||||
|
"""结构化字段保持精确匹配,只有 text 承担转义后的模糊搜索。"""
|
||||||
|
sdk, _executor = query_sdk
|
||||||
|
db.add(
|
||||||
|
_download_history(
|
||||||
|
"Exact Film Extended",
|
||||||
|
media_id="exact-download",
|
||||||
|
path="/exact/extended.mkv",
|
||||||
|
),
|
||||||
|
_download_history(
|
||||||
|
"Exact Film",
|
||||||
|
media_id="exact-download",
|
||||||
|
path="/exact/base.mkv",
|
||||||
|
),
|
||||||
|
_transfer_history(
|
||||||
|
"Exact Transfer Extended",
|
||||||
|
media_id="exact-transfer",
|
||||||
|
src="/exact/extended-src.mkv",
|
||||||
|
dest="/exact/extended-dest.mkv",
|
||||||
|
),
|
||||||
|
_transfer_history(
|
||||||
|
"Exact Transfer",
|
||||||
|
media_id="exact-transfer",
|
||||||
|
src="/exact/base-src.mkv",
|
||||||
|
dest="/exact/base-dest.mkv",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
exact_downloads = sdk.list_download_history(
|
||||||
|
DownloadHistoryFilter(
|
||||||
|
media_source=TMDB,
|
||||||
|
media_id="exact-download",
|
||||||
|
title="Exact Film",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert [item.path for item in exact_downloads.items] == ["/exact/base.mkv"]
|
||||||
|
fuzzy_downloads = sdk.list_download_history(
|
||||||
|
DownloadHistoryFilter(
|
||||||
|
media_source=TMDB,
|
||||||
|
media_id="exact-download",
|
||||||
|
text="extended",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert [item.path for item in fuzzy_downloads.items] == ["/exact/extended.mkv"]
|
||||||
|
|
||||||
|
exact_transfers = sdk.list_transfer_history(
|
||||||
|
TransferHistoryFilter(
|
||||||
|
media_source=TMDB,
|
||||||
|
media_id="exact-transfer",
|
||||||
|
title="Exact Transfer",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert [item.src for item in exact_transfers.items] == ["/exact/base-src.mkv"]
|
||||||
|
fuzzy_transfers = sdk.list_transfer_history(
|
||||||
|
TransferHistoryFilter(
|
||||||
|
media_source=TMDB,
|
||||||
|
media_id="exact-transfer",
|
||||||
|
text="extended-dest",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert [item.src for item in fuzzy_transfers.items] == ["/exact/extended-src.mkv"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_snapshots_normalize_legacy_identity_and_transfer_status():
|
||||||
|
"""旧半对身份和 NULL 整理状态不得使分页投影失败或产生假成功。"""
|
||||||
|
dirty_transfer = SimpleNamespace(
|
||||||
|
id=1,
|
||||||
|
media_source=TMDB,
|
||||||
|
media_id=" ",
|
||||||
|
status=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
snapshot = TransferHistorySnapshot.model_validate(dirty_transfer)
|
||||||
|
|
||||||
|
assert snapshot.media_source is None
|
||||||
|
assert snapshot.media_id is None
|
||||||
|
assert snapshot.status is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_snapshots_are_owned_by_the_sdk_contract_module():
|
||||||
|
"""公开查询返回值由独立快照定义,不复用宿主写入或 API 响应模型。"""
|
||||||
|
snapshots = (
|
||||||
|
SubscriptionSnapshot,
|
||||||
|
SubscriptionHistorySnapshot,
|
||||||
|
DownloadHistorySnapshot,
|
||||||
|
TransferHistorySnapshot,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert all(snapshot.__module__ == "app.schemas.query" for snapshot in snapshots)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -590,9 +675,7 @@ def test_sdk_get_returns_none_for_missing_records(query_sdk):
|
|||||||
def test_sdk_sync_async_semantics_match_and_async_uses_executor(db, query_sdk):
|
def test_sdk_sync_async_semantics_match_and_async_uses_executor(db, query_sdk):
|
||||||
"""四个查询门面的异步结果与同步一致,并交给 executor 线程。"""
|
"""四个查询门面的异步结果与同步一致,并交给 executor 线程。"""
|
||||||
sdk, executor = query_sdk
|
sdk, executor = query_sdk
|
||||||
subscribe = db.add(
|
subscribe = db.add(_subscribe("订阅同步异步", media_id="async-subscription"))
|
||||||
_subscribe("订阅同步异步", media_id="async-subscription")
|
|
||||||
)
|
|
||||||
subscribe_history = db.add(
|
subscribe_history = db.add(
|
||||||
_subscribe_history(
|
_subscribe_history(
|
||||||
"订阅历史同步异步",
|
"订阅历史同步异步",
|
||||||
|
|||||||
Reference in New Issue
Block a user