- 后端
- AI Agent
- MCP 服务
- AI 技能
【免费下载链接】MoviePilot
NAS媒体库自动化管理工具
导读
本文基于 MoviePilot 仓库docs/rules/07-naming-conventions.md整理而成,是该开源 NAS 媒体库自动化管理工具(涵盖下载、订阅、媒体服务器、消息通知、站点管理等模块)内部为所有新代码制定的统一命名约定。命名一致性是代码库无需注释即可传达意图的关键手段:阅读本文后,你将掌握 MoviePilot 中 Python 源文件、类、函数、变量、枚举、配置项、API 端点乃至消息/通知两个易混语义域的完整命名规则,理解每条约定背后的设计动机(如模块创建门禁、旧路径兼容策略),并能在贡献代码或二次开发时直接套用这些规范。文中所有示例与结论均可在当前仓库源码中找到对应实现。
一、总则:命名即沟通,规范即契约
MoviePilot 的命名约定遵循一条核心原则:一致的命名让代码库无需注释就能传达意图("Consistent naming is how the codebase communicates intent without comments")。所有新代码必须遵守以下约定,代码评审与架构测试以本规范为准绳。
命名规范共覆盖八大维度,每个维度都有明确的应用场景与反例对照:
- 文件与目录(Files)
- 类(Classes)
- 函数与方法(Functions and Methods)
- 变量与参数(Variables and Parameters)
- 枚举(Enums)
- 配置与设置(Configuration and Settings)
- API 端点与路由(API Endpoints and Routers)
- 消息/通知语义域边界(Message / Notification Domain Boundary)
其中「生产模块创建门禁」(Production Module Creation Gate)与「消息/通知语义域边界」是本规范中最具架构约束力的两条规则,分别对应docs/rules/07-naming-conventions.md中的两个强制决策流程。
二、文件与目录命名
2.1 基础文件命名规则
| 上下文 | 约定 | 示例 |
|---|---|---|
| Python 源文件 | snake_case.py | download.py、qbittorrent.py、package.py |
| 规范化能力包(canonical capability packages)中的新文件 | 优先使用单个小写职责名词;在添加同级文件前,先尝试扩展现有 owner | torrent.py、package.py、resources.py |
| 多文件能力 | 创建同名目录包,使用聚焦的单词子文件;禁止展平为<capability>_<role>.py平级文件 | transfer/workflow.py、transfer/execution.py |
| 模块包目录 | snake_case/;包根不重复导出宿主实现 | qbittorrent/、synologychat/、transfer/ |
| 测试文件 | test_<domain>.py | test_download_chain.py、test_subscribe_endpoint.py |
| Alembic 迁移 | 由 Alembic 自动生成,不要重命名 | 20240101_add_column.py |
| Skill 目录 | <kebab-case>/ | transfer-failed-retry/、moviepilot-cli/ |
这些约定在仓库中有大量真实对应物。例如测试目录tests/下可见 test_download_chain.py、test_subscribe_endpoint.py 等命名完全遵循test_<domain>.py;模块目录 app/modules/qbittorrent/ 为snake_case/包;而skills/目录下的 transfer-failed-retry、create-moviepilot-plugin 等则全部使用 kebab-case。
2.2 生产模块创建门禁(Production Module Creation Gate)
这是文件命名规则中最关键的一条强制决策流程。新增或拆分生产模块时必须按以下顺序决策,代码评审与架构测试以此为准:
- 优先扩展当前职责 owner。只因文件变长或需要一个私有辅助类,不得新建平级模块。
- 一个能力需要第二个生产文件时,必须在同一次变更中改为同名目录包,禁止继续增加
<capability>_<role>.py平级文件。 - 包内子文件使用准确、可独立说明职责的单个小写名词,例如
dependencies/profile.py与dependencies/native.py;不得使用native_dependencies.py、transfer_execution.py这类重复能力名的文件。 - 包根
__init__.py只允许包说明和确有外部契约证据的公开门面:不得复制实现,也不得为宿主代码重复导出子模块符号;宿主必须直接导入 owner 子模块。 - 旧路径兼容只能在确认真实插件消费者后,通过
app/sdk/或app/runtime/compat/做精确映射;不得为假设消费者保留旧源码、通配映射或双份导出。 - 新增文件前必须同步更新本规则对应的机器门禁;若现有门禁不能表达该命名约束,应在同一变更中补充 tests/test_architecture_dependencies.py。
这条门禁的落地依赖架构测试对命名约束的机器化检查。例如 tests/test_chain_base_boundary.py、tests/test_architecture_dependencies.py 等测试文件就是这类机器门禁的代表:它们以可执行测试的形式固化"哪些模块边界、哪些命名模式是允许的",防止后续变更悄悄引入违反规范的平级文件或重复导出。
反例与正例对照:
| 反例(Wrong) | 正例(Correct) |
|---|---|
transfer.py+transfer_execution.py | transfer/workflow.py+transfer/execution.py |
dependencies.py+native_dependencies.py | dependencies/profile.py+dependencies/native.py |
| 包根为旧路径做宿主 re-export | 精确的 SDK/Compat 映射;宿主代码直接导入 owner 子模块 |
三、类命名(PascalCase)
| 上下文 | 约定 | 示例 |
|---|---|---|
| Chain 类 | <Domain>Chain | DownloadChain、SearchChain、SubscribeChain |
| Module 类 | <Backend>Module | QbittorrentModule、EmbyModule、TelegramModule |
| Oper(数据访问)类 | <Model>Oper | SubscribeOper、SystemConfigOper、TransferHistoryOper |
| Helper 类 | <Domain>Helper | TorrentHelper、DirectoryHelper、MessageHelper |
| Pydantic schema 模型 | PascalCase,名词聚焦 | MediaInfo、TorrentInfo、DownloadingTorrent |
| SQLAlchemy 模型类 | PascalCase,单数名词 | Subscribe、TransferHistory、SystemConfig |
| 枚举类 | PascalCase | MediaType、EventType、ModuleType |
| Manager 类 | <Domain>Manager | ModuleManager、PluginManager、EventManager |
| 通用类 | PascalCase | MetaInfo、Context、ChainBase |
3.1 源码验证:三类核心类名的真实落点
- Chain 类:
DownloadChain定义于 app/chain/download/facade.py,SearchChain定义于 app/chain/search/facade.py,SubscribeChain定义于 app/chain/subscribe/facade.py,三者均继承自ChainBase。这印证了规范中「<Domain>Chain」的命名模式,且 Chain 实现以目录包形式组织。 - Module 类:
QbittorrentModule定义于 app/modules/qbittorrent/init.py,EmbyModule定义于 app/modules/emby/init.py,TelegramModule定义于 app/modules/telegram/module.py。注意 Module 类的命名使用完整后端名(Qbittorrent而非QB),这正是反例表中class QBModule:被禁止的原因。 - Oper 类:
SubscribeOper定义于 app/db/oper/subscribe.py,SystemConfigOper定义于 app/db/oper/systemconfig.py,TransferHistoryOper定义于 app/db/oper/transferhistory.py。它们均继承自DbOper,是 MoviePilot 数据访问层的标准形态。 - Helper 类:
NotificationHelper定义于 app/application/notification.py,继承自ServiceBaseHelper[NotificationConf],对应规范中<Domain>Helper的约定。 - SQLAlchemy 模型:
Message模型定义于 app/db/models/message.py,使用PascalCase单数名词。
从源码结构可以推断:Chain 类以<Domain>Chain统一后缀、Oper 类以<Model>Oper统一后缀、Manager 类以<Domain>Manager统一后缀,这套命名把"类名即职责定位"落实到极致——看到名字即可知道该类的分层归属(链、模块、数据访问、帮助器、管理)。
四、函数与方法命名(snake_case)
| 上下文 | 约定 | 示例 |
|---|---|---|
| 所有函数与方法 | snake_case | get_subscribe、run_module、on_config_changed |
| 私有方法 | _snake_case(前导下划线) | _submit_download_added_task、_parse_result |
| 事件处理方法 | on_<event_name>或描述性命名 | on_transfer_complete、handle_config_changed |
| 模块接口方法 | 匹配_ModuleBase契约 | init_module、init_setting、get_name、get_type、test、stop |
| Oper 方法 | 动词 + 名词 | get、add、update、delete、list |
4.1 设计要点解读
- 事件处理方法使用
on_<event_name>前缀是 MoviePilot 事件驱动架构的显性表达。事件处理函数必须一眼看出"它响应哪个事件"(如on_transfer_complete响应转移完成事件)。这也与docs/rules/04-design-patterns.md中事件驱动模式相呼应。 - 模块接口方法必须严格匹配
_ModuleBase契约,这是各模块(下载器、媒体服务器、消息渠道等)可被统一调度器加载的前提。方法名是契约的一部分,不得随意更改。 - Oper 方法采用动词 + 名词的极简风格(
get、add、update、delete、list),与数据访问层的 CRUD 语义一一对应。
反例:def GetSubscribe():(错误,应为def get_subscribe():);def handleConfigChanged():(错误,应为def on_config_changed():或def handle_config_changed():)。
五、变量与参数命名
| 上下文 | 约定 | 示例 |
|---|---|---|
| 局部变量 | snake_case | torrent_info、media_type、download_dir |
| 实例属性 | snake_case | self.download_history、self.config |
| 常量(模块级) | UPPER_SNAKE_CASE | DEFAULT_EVENT_PRIORITY、MIN_EVENT_CONSUMER_THREADS |
| 私有变量 | _snake_case(前导下划线) | _instance、_lock |
| 类型变量 | PascalCase搭配TypeVar | T = TypeVar("T") |
反例:TORRENT_info = ...(错误,应为torrent_info = ...)。命名中不区分大小写混写,私有性统一由前导下划线表达。
六、枚举命名
| 上下文 | 约定 | 示例 |
|---|---|---|
| 枚举类名 | PascalCase | MediaType、TorrentStatus、EventType |
| 枚举成员 | PascalCase(针对复杂枚举) | MediaType.MOVIE、EventType.TransferComplete |
| 字符串枚举值 | 匹配领域语言 | MediaType.MOVIE = '电影'、TorrentStatus.TRANSFER = '可转移' |
SystemConfigKey值 | 匹配配置键的字符串原值 | SystemConfigKey.RssUrls = "RssUrls" |
6.1 源码验证:枚举值即领域语言
在 app/schemas/types.py 中可以找到规范的典型实现:
MessageType(app/schemas/types.py)的成员均为PascalCase,而字符串值使用中文领域语言:Download = "资源下载"、Organize = "整理入库"、Subscribe = "订阅"、SiteMessage = "站点"、Manual = "手动处理"、Plugin = "插件"、Agent = "智能体"、Other = "其它"。NotificationChannel(app/schemas/types.py)的成员同样遵循PascalCase,字符串值为渠道领域名:Wechat = "微信"、Feishu = "飞书"、Telegram = "Telegram"、Slack = "Slack"、Discord = "Discord"、DingTalk = "钉钉"、SynologyChat = "SynologyChat"、Web = "Web"等。SystemConfigKey(app/schemas/types.py)定义于app/schemas/types.py中的"系统配置Key字典"区块,其成员名使用PascalCase,而值必须与持久化配置键的字符串完全一致,例如Downloaders = "Downloaders"、MediaServers = "MediaServers"、Notifications = "Notifications"、Directories = "Directories"、RssSites = "RssSites"、AIAgentConfig = "AIAgentConfig"。
这一约定的深层原因是:SystemConfigKey的枚举值是持久化到数据库的配置主键,一旦改名会破坏存量用户数据,因此成员名可以遵循代码命名规范,但值必须冻结为配置键原字符串。
七、配置与设置命名
| 上下文 | 约定 | 示例 |
|---|---|---|
Settings/ConfigModel字段 | UPPER_SNAKE_CASE | API_TOKEN、LLM_MODEL、QB_HOST |
SystemConfigKey枚举成员 | PascalCase | SystemConfigKey.RssUrls、SystemConfigKey.SubscribeFilter |
| 环境变量名 | UPPER_SNAKE_CASE | AI_AGENT_ENABLE、DB_TYPE |
关键实践:访问配置时必须通过SystemConfigKey枚举成员而非裸字符串。反例:configuration.get("RssUrls");正例:configuration.get(SystemConfigKey.RssUrls)。
这条规则的价值在于:枚举将配置键集中管理,杜绝散落各处的魔法字符串,配合 IDE 的类型检查与自动补全,任何配置键的拼写错误都能在编译期暴露。同时它保证了配置键的持久化值与外部协议(DB、环境变量)冻结的一致性。
八、API 端点与路由命名
| 上下文 | 约定 | 示例 |
|---|---|---|
| 端点函数名 | snake_case,动词前置 | get_subscribe_list、add_download、delete_history |
| URL 路径段 | kebab-case或snake_case,匹配既有模式 | /api/v1/subscribe、/api/v1/transfer/history |
| Router tags | 匹配资源领域名 | "subscribe"、"download"、"media" |
设计要点:
- 动词前置让 API 处理函数在路由注册处可读性最强,
get_、add_、delete_、update_前缀清晰表达 HTTP 语义与资源操作。 - URL 路径段允许
kebab-case或snake_case,但必须与项目既有模式保持一致——这是一个"向后兼容优先"的约定,避免新旧路由风格并存造成混乱。 - Router tags 直接取资源领域名(
subscribe、download、media),这使 OpenAPI 文档中相同领域的所有端点聚合在同一个 tag 下,便于 API 使用者检索。
九、Message / Notification 语义域边界(强制规则)
message与notification在 MoviePilot 中是两个不同的语义域。新增或修改相关代码时必须按职责选名,不得混用。这是本规范中最易踩坑、也最具业务约束力的一条规则。
9.1 两个语义域的职责划分
| 语义域 | 职责 | 规范命名示例 |
|---|---|---|
notification | 通知渠道能力:渠道枚举、渠道配置、渠道发现、渠道管理、渠道能力描述 | NotificationChannel、NotificationConf、NotificationHelper、NotificationChain、NotificationAction、ChannelCapabilityManager、ModuleType.Notification、channel_manage |
message | 各渠道发送或接收的消息:消息体、消息类型、消息链、消息历史、消息队列 | Message、MessageType、IncomingMessage、MessageChain、MessageHistoryItem、MessageOper、post_message、message_parser |
9.2 判断规则
| 规则 | 说明 |
|---|---|
渠道本身用notification | 渠道是能力提供方,如NotificationChannel枚举、NotificationConf渠道配置 |
消息内容与收发用message | 消息是被传输的内容,如发送体Message、接收体IncomingMessage、分类MessageType |
| 渠道 × 消息的交叉概念按主导方判断 | 按渠道控制消息开关的NotificationSwitch属渠道能力;消息历史清理MessageClearScope属消息 |
| 历史旧名不在源码保留 | Notification、MessageChannel、NotificationType、CommingMessage等旧名仅登记在app/runtime/compat/manifest.py的SYMBOL_ALIASES,新代码一律使用规范名 |
| 持久化值与外部协议冻结 | 枚举值、SystemConfigKey配置值、DB 表名、API 路径、外部平台字段(如 Jellyfin 的NotificationType)不随命名统一变更 |
9.3 源码验证:语义域的真实落点与旧名兼容
message 域:
MessageChain定义于 app/chain/message.py(继承ChainBase);MessageOper定义于 app/db/oper/message.py;Message(发送体)与IncomingMessage(接收体)均定义于 app/schemas/message.py(IncomingMessage在第 96 行,继承BaseModel)。notification 域:
NotificationConf定义于 app/schemas/system.py;NotificationHelper定义于 app/application/notification.py,负责渠道能力的服务化封装。旧名兼容:
SYMBOL_ALIASES注册表位于 app/runtime/compat/manifest.py,其中_MESSAGE_NOTIFICATION_SYMBOL_ALIASES(app/runtime/compat/manifest.py)专门登记了消息/通知命名统一后的旧符号映射,例如:MessageChannel→NotificationChannel(app.schemas.types)NotificationType→MessageType(app.schemas.types)Notification→Message(app.schemas.message)CommingMessage→IncomingMessage(app.schemas.message)NotificationHistoryItem→MessageHistoryItemNotificationClearScope/ClearBefore/ClearData→MessageClearScope/ClearBefore/ClearDataChannelCapability、ChannelCapabilities、ChannelCapabilityManager→app.schemas.notification
该注册表由 app/runtime/compat/imports.py 等运行时兼容层消费,实现"旧名可导入但指向新符号"的精确映射。从源码结构可以推断:这套机制是仅为真实插件消费者保留的兼容通道,符合文档中"旧路径兼容只能在确认真实插件消费者后,通过
app/sdk/或app/runtime/compat/做精确映射"的约束。
9.4 反例对照
| 反例(Wrong,新代码中禁止) | 正例(Correct) |
|---|---|
MessageChannel.Telegram | NotificationChannel.Telegram |
Notification(title=...) | Message(title=...) |
十、反模式速查(Anti-Patterns)
将上述全部约定浓缩为一张"错误 → 正确"对照表,供代码评审时快速比对:
| 错误(Wrong) | 正确(Correct) |
|---|---|
class downloadchain: | class DownloadChain: |
class QBModule: | class QbittorrentModule: |
def GetSubscribe(): | def get_subscribe(): |
TORRENT_info = ... | torrent_info = ... |
def handleConfigChanged(): | def on_config_changed():或def handle_config_changed(): |
configuration.get("RssUrls") | configuration.get(SystemConfigKey.RssUrls) |
class subscribe_oper: | class SubscribeOper: |
transfer.py+transfer_execution.py | transfer/workflow.py+transfer/execution.py |
dependencies.py+native_dependencies.py | dependencies/profile.py+dependencies/native.py |
| 包根为旧路径做宿主 re-export | 精确的 SDK/Compat 映射;宿主代码直接导入 owner 子模块 |
MessageChannel.Telegram(新代码) | NotificationChannel.Telegram |
Notification(title=...)(新代码) | Message(title=...) |
十一、如何在贡献中落实这些规范
- 命名先行:在写第一行代码前,先判断新增文件属于哪个能力域,按「生产模块创建门禁」的决策顺序确定是扩展现有 owner 还是新建同名目录包。
- 用测试固化约束:若新增的命名约束无法被现有机器门禁表达,应在同一变更中补充 tests/test_architecture_dependencies.py 之类的架构测试,让规范可被 CI 自动校验。
- 严格遵守语义域:涉及消息/通知的代码,先判断职责是"渠道能力"(notification)还是"消息收发内容"(message),再决定命名域;绝不把旧名引入新代码。
- 尊重冻结契约:枚举值、
SystemConfigKey配置值、DB 表名、API 路径等持久化或对外协议层面的命名不随代码重构变更;旧符号兼容一律走app/runtime/compat/manifest.py的SYMBOL_ALIASES精确映射。
以上规范共同构成了 MoviePilot 代码库的"命名宪法"——它不仅是风格的统一,更是模块边界、数据访问分层、事件驱动架构与消息/通知语义域在命名层面上的制度化表达。遵循这些约定,任何新代码都能被团队与自动化门禁准确理解与校验。
本文依据docs/rules/07-naming-conventions.md(Last Updated: 2026-08-29)整理,并结合仓库源码与测试验证。规范细节以文档原文与仓库实际代码为准。
- 后端
- AI Agent
- MCP 服务
- AI 技能
【免费下载链接】MoviePilot
NAS媒体库自动化管理工具
相关推荐
Modin 社区代码规范:从命名约定到文档注释的全方位指南
Modin 社区代码规范:从命名约定到文档注释的全方位指南 引言 你是否在参与开源项目时因代码风格不统一而感到困扰?是否曾因文档注释不清晰而难以理解函数功能?本
数据分析数据工程大数据MicroPython 代码规范与提交约定全指南:从 Commit Message 到自动格式化
MicroPython 代码规范与提交约定全指南:从 Commit Message 到自动格式化 本指南完整解析 MicroPython 仓库的 CODECON
嵌入式语言运行时编程语言解释器编译器物联网系统编程Ghost Downloader 代码规范与架构约定:从命名词汇表到领域语言的工程实践
Ghost Downloader 代码规范与架构约定:从命名词汇表到领域语言的工程实践 导读 Ghost Downloader( Ghost Downloade
桌面应用网络
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考