mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
refactor: add transaction ownership ratchet
This commit is contained in:
@@ -0,0 +1,921 @@
|
||||
{
|
||||
"model_decorators": {
|
||||
"by_kind": {
|
||||
"async_db_query": 49,
|
||||
"async_db_update": 13,
|
||||
"db_query": 75,
|
||||
"db_update": 41
|
||||
},
|
||||
"count": 178,
|
||||
"methods": [
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/agentchat.py",
|
||||
"method": "AgentChat.async_get_by_session"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/agentchat.py",
|
||||
"method": "AgentChat.async_list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/agentchat.py",
|
||||
"method": "AgentChat.get_by_session"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/agentchat.py",
|
||||
"method": "AgentChat.list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/agenttask.py",
|
||||
"method": "AgentTask.add_task"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/agenttask.py",
|
||||
"method": "AgentTask.get_for_user"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/agenttask.py",
|
||||
"method": "AgentTask.list_for_user"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/agenttask.py",
|
||||
"method": "AgentTask.update_task"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/agenttaskrun.py",
|
||||
"method": "AgentTaskRun.begin_run"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/agenttaskrun.py",
|
||||
"method": "AgentTaskRun.delete_task_and_runs"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/agenttaskrun.py",
|
||||
"method": "AgentTaskRun.finish_run"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/agenttaskrun.py",
|
||||
"method": "AgentTaskRun.get_by_run_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/agenttaskrun.py",
|
||||
"method": "AgentTaskRun.interrupt_task"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/agenttaskrun.py",
|
||||
"method": "AgentTaskRun.list_for_task"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/downloadfailure.py",
|
||||
"method": "DownloadFailure.delete_expired"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadfailure.py",
|
||||
"method": "DownloadFailure.get_active_by_fingerprints"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/downloadfailure.py",
|
||||
"method": "DownloadFailure.record_failure"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadFiles.delete_by_fullpath"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadFiles.delete_orphans"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadFiles.get_by_fullpath"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadFiles.get_by_hash"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadFiles.get_by_savepath"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.async_count"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.async_count_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.async_list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.async_list_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.delete_before"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.get_by_hash"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.get_by_hashes"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.get_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.get_by_path"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.get_last_by"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.list_by_date"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.list_by_type"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/downloadhistory.py",
|
||||
"method": "DownloadHistory.list_by_user_date"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.async_exist_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.async_exists_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.async_get_by_itemid"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.delete_excluded_servers"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.delete_stale"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.empty"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.exist_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.exists_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.get_by_itemid"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/mediaserver.py",
|
||||
"method": "MediaServerItem.get_by_server_itemid"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/message.py",
|
||||
"method": "Message.async_list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/message.py",
|
||||
"method": "Message.async_list_sent_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/message.py",
|
||||
"method": "Message.create_and_to_dict"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/message.py",
|
||||
"method": "Message.delete_before"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/message.py",
|
||||
"method": "Message.exists_by_source"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/message.py",
|
||||
"method": "Message.list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.async_delete_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.async_get_by_credential_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.async_get_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.async_get_by_user_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.async_update_last_used"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.delete_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.get_by_credential_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.get_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.get_by_user_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/passkey.py",
|
||||
"method": "PassKey.update_last_used"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.async_get_plugin_data"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.async_get_plugin_data_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.async_get_plugin_data_by_plugin_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.del_plugin_data"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.del_plugin_data_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.get_plugin_data"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.get_plugin_data_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/plugindata.py",
|
||||
"method": "PluginData.get_plugin_data_by_plugin_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.async_get_actives"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.async_get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.async_get_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.async_list_order_by_pri"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.async_reset"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.get_actives"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.get_domains_by_ids"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.list_order_by_pri"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/site.py",
|
||||
"method": "Site.reset"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/siteicon.py",
|
||||
"method": "SiteIcon.async_get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/siteicon.py",
|
||||
"method": "SiteIcon.get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/sitestatistic.py",
|
||||
"method": "SiteStatistic.async_get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/sitestatistic.py",
|
||||
"method": "SiteStatistic.get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/sitestatistic.py",
|
||||
"method": "SiteStatistic.reset"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.async_get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.async_get_latest"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.delete_before"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.get_by_date"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.get_by_domain"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/siteuserdata.py",
|
||||
"method": "SiteUserData.get_latest"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_delete_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_exists"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_exists_by_username"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_get_by"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_get_by_state"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_get_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_list_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_list_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_list_by_type"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.async_list_by_username"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.delete_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.exists"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.exists_by_username"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.get_by"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.get_by_state"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.get_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.list_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.list_by_type"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribe.py",
|
||||
"method": "Subscribe.list_by_username"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribehistory.py",
|
||||
"method": "SubscribeHistory.async_exists"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribehistory.py",
|
||||
"method": "SubscribeHistory.async_list_by_type"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/subscribehistory.py",
|
||||
"method": "SubscribeHistory.async_list_by_type_and_username"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribehistory.py",
|
||||
"method": "SubscribeHistory.exists"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/subscribehistory.py",
|
||||
"method": "SubscribeHistory.list_by_type"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/systemconfig.py",
|
||||
"method": "SystemConfig.async_get_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/systemconfig.py",
|
||||
"method": "SystemConfig.delete_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/systemconfig.py",
|
||||
"method": "SystemConfig.get_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.async_count"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.async_count_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.async_list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.async_list_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.async_statistic"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.count"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.count_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.delete_before"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.get_by_dest"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.get_by_hash"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.get_by_media_identity"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.get_by_src"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.get_success_by_src"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.list_by"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.list_by_date"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.list_by_hash"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.list_by_page"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.list_by_title"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.list_success_by_src"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.list_success_move_by_dest"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.monthly_media_statistics"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.replace_by_src"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.statistic"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/transferhistory.py",
|
||||
"method": "TransferHistory.update_download_hash"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/transferpending.py",
|
||||
"method": "TransferPending.clear"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/transferpending.py",
|
||||
"method": "TransferPending.discard"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/transferpending.py",
|
||||
"method": "TransferPending.list_all"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/transferpending.py",
|
||||
"method": "TransferPending.register"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.async_delete_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.async_delete_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.async_get_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.async_get_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.async_update_otp_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.delete_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.delete_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.get_by_id"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.get_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/user.py",
|
||||
"method": "User.update_otp_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/userconfig.py",
|
||||
"method": "UserConfig.delete_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/userconfig.py",
|
||||
"method": "UserConfig.get_by_key"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_fail"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_get_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_get_enabled_workflows"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_get_event_triggered_workflows"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_get_timer_triggered_workflows"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_reset"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_start"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_success"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_update_current_action"
|
||||
},
|
||||
{
|
||||
"decorator": "async_db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.async_update_state"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.fail"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.get_by_name"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.get_enabled_workflows"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.get_event_triggered_workflows"
|
||||
},
|
||||
{
|
||||
"decorator": "db_query",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.get_timer_triggered_workflows"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.reset"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.start"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.success"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.update_current_action"
|
||||
},
|
||||
{
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/workflow.py",
|
||||
"method": "Workflow.update_state"
|
||||
}
|
||||
]
|
||||
},
|
||||
"model_session_factories": {
|
||||
"calls": [],
|
||||
"count": 0
|
||||
},
|
||||
"model_transaction_calls": {
|
||||
"calls": [],
|
||||
"count": 0
|
||||
},
|
||||
"oper_session_factories": {
|
||||
"calls": [],
|
||||
"count": 0
|
||||
},
|
||||
"oper_transaction_calls": {
|
||||
"calls": [],
|
||||
"count": 0
|
||||
},
|
||||
"schema_version": 1,
|
||||
"scope": "app/db/models and app/db/oper transaction ownership debt"
|
||||
}
|
||||
@@ -45,6 +45,28 @@ def _performance_sample(*, loaded_module_count: int = 10) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _transaction_sample(methods: list[dict[str, str]]) -> dict:
|
||||
"""构造最小事务债务 fixture,供单向 ratchet 行为测试。"""
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"scope": "app/db/models and app/db/oper transaction ownership debt",
|
||||
"model_decorators": {
|
||||
"count": len(methods),
|
||||
"by_kind": {
|
||||
"async_db_query": 0,
|
||||
"async_db_update": 0,
|
||||
"db_query": 0,
|
||||
"db_update": len(methods),
|
||||
},
|
||||
"methods": methods,
|
||||
},
|
||||
"model_transaction_calls": {"count": 0, "calls": []},
|
||||
"model_session_factories": {"count": 0, "calls": []},
|
||||
"oper_transaction_calls": {"count": 0, "calls": []},
|
||||
"oper_session_factories": {"count": 0, "calls": []},
|
||||
}
|
||||
|
||||
|
||||
def test_architecture_legacy_action_requires_scope(capsys):
|
||||
"""旧操作未明确宿主或插件范围时必须拒绝执行。"""
|
||||
with pytest.raises(SystemExit) as error:
|
||||
@@ -209,6 +231,34 @@ def test_plugin_v2_fixture_migrates_before_semantic_comparison(tmp_path: Path):
|
||||
) == architecture_baseline.semantic_baseline(baseline_path, new_value)
|
||||
|
||||
|
||||
def test_transaction_ratchet_allows_removal_but_rejects_new_method() -> None:
|
||||
"""事务债务低水位允许下降,替换或新增 Model 自动事务仍必须失败。"""
|
||||
first = {
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/demo.py",
|
||||
"method": "Demo.save",
|
||||
}
|
||||
second = {
|
||||
"decorator": "db_update",
|
||||
"file": "app/db/models/demo.py",
|
||||
"method": "Demo.delete",
|
||||
}
|
||||
expected = _transaction_sample([first])
|
||||
|
||||
assert architecture_baseline.transaction_ratchet_matches(
|
||||
expected,
|
||||
_transaction_sample([]),
|
||||
)
|
||||
assert not architecture_baseline.transaction_ratchet_matches(
|
||||
expected,
|
||||
_transaction_sample([first, second]),
|
||||
)
|
||||
assert not architecture_baseline.transaction_ratchet_matches(
|
||||
expected,
|
||||
_transaction_sample([second]),
|
||||
)
|
||||
|
||||
|
||||
def test_architecture_write_host_only_updates_host_files(
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
@@ -217,6 +267,7 @@ def test_architecture_write_host_only_updates_host_files(
|
||||
"""宿主写操作不得连带覆盖官方插件 fixture。"""
|
||||
dependency_path = tmp_path / "dependency.json"
|
||||
runtime_path = tmp_path / "runtime.json"
|
||||
transaction_path = tmp_path / "transaction.json"
|
||||
plugin_path = tmp_path / "plugin.json"
|
||||
monkeypatch.setattr(
|
||||
architecture_baseline,
|
||||
@@ -228,6 +279,11 @@ def test_architecture_write_host_only_updates_host_files(
|
||||
"RUNTIME_BASELINE_PATH",
|
||||
runtime_path,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
architecture_baseline,
|
||||
"TRANSACTION_BASELINE_PATH",
|
||||
transaction_path,
|
||||
)
|
||||
monkeypatch.setattr(architecture_baseline, "PLUGIN_BASELINE_PATH", plugin_path)
|
||||
monkeypatch.setattr(
|
||||
architecture_baseline,
|
||||
@@ -239,16 +295,25 @@ def test_architecture_write_host_only_updates_host_files(
|
||||
"collect_runtime_baseline",
|
||||
lambda: {"scope": "host-runtime"},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
architecture_baseline,
|
||||
"collect_transaction_debt_baseline",
|
||||
lambda: {"scope": "host-transaction"},
|
||||
)
|
||||
|
||||
assert architecture_baseline.main(["--write-host"]) == 0
|
||||
|
||||
assert json.loads(dependency_path.read_text()) == {"scope": "host-dependency"}
|
||||
assert json.loads(runtime_path.read_text()) == {"scope": "host-runtime"}
|
||||
assert json.loads(transaction_path.read_text()) == {
|
||||
"scope": "host-transaction"
|
||||
}
|
||||
assert not plugin_path.exists()
|
||||
output = capsys.readouterr().out
|
||||
assert "即将写入" in output
|
||||
assert "dependency.json" in output
|
||||
assert "runtime.json" in output
|
||||
assert "transaction.json" in output
|
||||
|
||||
|
||||
def test_architecture_write_plugins_only_updates_plugin_file(
|
||||
@@ -261,6 +326,7 @@ def test_architecture_write_plugins_only_updates_plugin_file(
|
||||
(plugin_repo / "plugins.v3").mkdir()
|
||||
dependency_path = tmp_path / "dependency.json"
|
||||
runtime_path = tmp_path / "runtime.json"
|
||||
transaction_path = tmp_path / "transaction.json"
|
||||
plugin_path = tmp_path / "plugin.json"
|
||||
monkeypatch.setattr(
|
||||
architecture_baseline,
|
||||
@@ -272,6 +338,11 @@ def test_architecture_write_plugins_only_updates_plugin_file(
|
||||
"RUNTIME_BASELINE_PATH",
|
||||
runtime_path,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
architecture_baseline,
|
||||
"TRANSACTION_BASELINE_PATH",
|
||||
transaction_path,
|
||||
)
|
||||
monkeypatch.setattr(architecture_baseline, "PLUGIN_BASELINE_PATH", plugin_path)
|
||||
|
||||
assert architecture_baseline.main(
|
||||
@@ -281,6 +352,7 @@ def test_architecture_write_plugins_only_updates_plugin_file(
|
||||
assert plugin_path.is_file()
|
||||
assert not dependency_path.exists()
|
||||
assert not runtime_path.exists()
|
||||
assert not transaction_path.exists()
|
||||
|
||||
|
||||
def test_architecture_plugin_check_writes_review_report_only_when_requested(
|
||||
|
||||
@@ -16,6 +16,7 @@ def test_architecture_contract_baselines_match_current_source():
|
||||
baseline_paths = (
|
||||
BASELINE_ROOT / "dependency-baseline.json",
|
||||
BASELINE_ROOT / "runtime-contract-baseline.json",
|
||||
BASELINE_ROOT / "transaction-debt-baseline.json",
|
||||
)
|
||||
contents_before = {
|
||||
path: path.read_bytes()
|
||||
@@ -56,6 +57,18 @@ def test_official_plugin_baseline_records_external_source():
|
||||
)
|
||||
|
||||
|
||||
def test_dependency_baseline_records_nonempty_host_graph() -> None:
|
||||
"""宿主依赖 fixture 不得因收集器提前返回而被静默写成空值。"""
|
||||
baseline_path = BASELINE_ROOT / "dependency-baseline.json"
|
||||
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
||||
|
||||
assert baseline["schema_version"] == 1
|
||||
assert baseline["module_count"] == len(baseline["modules"])
|
||||
assert baseline["edge_count"] == len(baseline["edges"])
|
||||
assert baseline["module_count"] > 0
|
||||
assert baseline["edge_count"] > 0
|
||||
|
||||
|
||||
def test_official_discovery_plugins_explicitly_keep_host_page_envelope():
|
||||
"""宿主探索页消费的官方插件 API 不得依赖动态路由隐式包装。"""
|
||||
baseline_path = BASELINE_ROOT / "official-plugin-baseline.json"
|
||||
@@ -105,6 +118,20 @@ def test_runtime_contract_baseline_excludes_diagnostic_line_numbers():
|
||||
assert '"line"' not in json.dumps(baseline)
|
||||
|
||||
|
||||
def test_transaction_debt_baseline_is_a_model_and_oper_ratchet() -> None:
|
||||
"""事务 fixture 必须冻结存量 Model 自动提交,并保持 Oper 自提交为零。"""
|
||||
baseline_path = BASELINE_ROOT / "transaction-debt-baseline.json"
|
||||
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
||||
|
||||
assert baseline["schema_version"] == 1
|
||||
assert baseline["model_decorators"]["count"] == 178
|
||||
assert sum(baseline["model_decorators"]["by_kind"].values()) == 178
|
||||
assert baseline["model_transaction_calls"] == {"count": 0, "calls": []}
|
||||
assert baseline["model_session_factories"] == {"count": 0, "calls": []}
|
||||
assert baseline["oper_transaction_calls"] == {"count": 0, "calls": []}
|
||||
assert baseline["oper_session_factories"] == {"count": 0, "calls": []}
|
||||
|
||||
|
||||
def test_startup_performance_baseline_records_normal_and_safe_lifecycle_resources():
|
||||
"""非功能基线必须同时记录正常/安全模式和隔离资源增量。"""
|
||||
baseline_path = BASELINE_ROOT / "startup-performance-baseline.json"
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
缓存的池化引擎,漏掉任何一类都是连接泄漏。
|
||||
"""
|
||||
import asyncio
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
from app.runtime.config import global_vars, settings
|
||||
from app.db import engine as engine_module
|
||||
@@ -90,6 +93,69 @@ def test_get_async_db_yields_session_from_scope(monkeypatch):
|
||||
assert used == ["enter", "exit"], "会话作用域未正确进入/退出"
|
||||
|
||||
|
||||
def test_scoped_sessions_are_not_shared_across_worker_threads(monkeypatch):
|
||||
"""同步入口必须为并行工作线程提供不同 Session 实例。"""
|
||||
registry = scoped_session(sessionmaker())
|
||||
barrier = threading.Barrier(2)
|
||||
monkeypatch.setattr(session_module, "_scoped_session", registry)
|
||||
|
||||
def open_in_thread() -> int:
|
||||
"""在线程内持有会话直到另一个线程也完成解析。"""
|
||||
session = session_module.ScopedSession()
|
||||
try:
|
||||
barrier.wait(timeout=5)
|
||||
return id(session)
|
||||
finally:
|
||||
registry.remove()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
session_ids = list(executor.map(lambda _: open_in_thread(), range(2)))
|
||||
|
||||
assert len(set(session_ids)) == 2
|
||||
|
||||
|
||||
def test_async_session_scopes_are_not_shared_across_tasks(monkeypatch):
|
||||
"""并发异步任务必须各自创建和关闭 AsyncSession 作用域。"""
|
||||
created: list[object] = []
|
||||
|
||||
class FakeAsyncSession:
|
||||
"""记录每次作用域构造的独立异步会话替身。"""
|
||||
|
||||
def __init__(self, **_kwargs) -> None:
|
||||
"""创建可由异步上下文管理器返回的唯一实例。"""
|
||||
created.append(self)
|
||||
|
||||
async def __aenter__(self):
|
||||
"""返回当前会话实例。"""
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_exc) -> bool:
|
||||
"""模拟正常释放且不吞掉异常。"""
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(
|
||||
session_module,
|
||||
"_resolve_async_engine",
|
||||
lambda: (object(), True),
|
||||
)
|
||||
monkeypatch.setattr(session_module, "AsyncSession", FakeAsyncSession)
|
||||
|
||||
async def open_in_task() -> int:
|
||||
"""进入一个任务私有的异步会话作用域。"""
|
||||
async with session_module.async_session_scope() as session:
|
||||
await asyncio.sleep(0)
|
||||
return id(session)
|
||||
|
||||
async def run() -> list[int]:
|
||||
"""并发执行两个会话作用域。"""
|
||||
return await asyncio.gather(open_in_task(), open_in_task())
|
||||
|
||||
session_ids = asyncio.run(run())
|
||||
|
||||
assert len(created) == 2
|
||||
assert len(set(session_ids)) == 2
|
||||
|
||||
|
||||
def test_close_database_disposes_pooled_engines(monkeypatch):
|
||||
"""
|
||||
close_database 必须释放按事件循环缓存的池化引擎。
|
||||
|
||||
Reference in New Issue
Block a user