-
Notifications
You must be signed in to change notification settings - Fork 230
feat: 插件管理 #2097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: 插件管理 #2097
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -139,10 +139,35 @@ def _scan_directory( | |||||||||||||||||||||||||||||||||||||||||||
| non_default_factories: list[ApplicationFactory] = [] | ||||||||||||||||||||||||||||||||||||||||||||
| default_factories: list[ApplicationFactory] = [] | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # 一次性扫描所有 .py 文件,按后缀分组 | ||||||||||||||||||||||||||||||||||||||||||||
| # 内置应用递归扫描;第三方插件只扫描每个真实插件根的第一层。 | ||||||||||||||||||||||||||||||||||||||||||||
| if source == PluginSource.THIRD_PARTY: | ||||||||||||||||||||||||||||||||||||||||||||
| python_files: list[Path] = [] | ||||||||||||||||||||||||||||||||||||||||||||
| if directory.is_dir(): | ||||||||||||||||||||||||||||||||||||||||||||
| directory_resolved = directory.resolve(strict=True) | ||||||||||||||||||||||||||||||||||||||||||||
| for plugin_dir in directory.iterdir(): | ||||||||||||||||||||||||||||||||||||||||||||
| if plugin_dir.is_symlink() or not plugin_dir.is_dir(): | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||
| plugin_dir_resolved = plugin_dir.resolve(strict=True) | ||||||||||||||||||||||||||||||||||||||||||||
| except (OSError, RuntimeError): | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| if plugin_dir_resolved != directory_resolved / plugin_dir.name: | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+147
to
+155
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
curl -fsSL 'https://docs.python.org/3.11/library/pathlib.html' \
| grep -F 'The children are yielded in arbitrary order'
rg -n -C 4 'iterdir\(\)|重复的 APP_ID|先注册者胜' \
src/one_dragon/base/operation/application/application_factory_manager.py \
docs/develop/one_dragon/modules/application_plugin_system.mdRepository: OneDragon-Anything/ZenlessZoneZero-OneDragon Length of output: 4444 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate structure ---'
ast-grep outline src/one_dragon/base/operation/application/application_factory_manager.py
printf '%s\n' '--- scan and registration symbols ---'
rg -n -C 8 \
'def (scan|_scan|_register_plugin_metadata|_read_plugin_metadata)|_register_plugin_metadata\(|_read_plugin_metadata\(|_plugin_infos|scan_failures' \
src/one_dragon/base/operation/application/application_factory_manager.py
printf '%s\n' '--- relevant source sections ---'
sed -n '110,190p' src/one_dragon/base/operation/application/application_factory_manager.py
sed -n '360,455p' src/one_dragon/base/operation/application/application_factory_manager.pyRepository: OneDragon-Anything/ZenlessZoneZero-OneDragon Length of output: 14038 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from dataclasses import dataclass
`@dataclass`(frozen=True)
class Plugin:
directory: str
app_id: str
def scan(order: list[Plugin]) -> tuple[str, list[str]]:
registered: dict[str, Plugin] = {}
failures: list[str] = []
for plugin in order:
if plugin.app_id in registered:
failures.append(plugin.directory)
continue
registered[plugin.app_id] = plugin
winner = registered["same"].directory
return winner, failures
plugins = [
Plugin("Zoo", "same"),
Plugin("alpha", "same"),
]
sorted_plugins = sorted(plugins, key=lambda plugin: (plugin.directory.casefold(), plugin.directory))
assert scan(plugins) == ("Zoo", ["alpha"])
assert scan(list(reversed(plugins))) == ("alpha", ["Zoo"])
assert scan(sorted_plugins) == ("alpha", ["Zoo"])
print("unsorted order 1:", scan(plugins))
print("unsorted order 2:", scan(list(reversed(plugins))))
print("proposed sorted order:", [plugin.directory for plugin in sorted_plugins])
print("proposed sorted result:", scan(sorted_plugins))
PYRepository: OneDragon-Anything/ZenlessZoneZero-OneDragon Length of output: 339 稳定第三方插件的加载顺序。
建议的排序方式- for plugin_dir in directory.iterdir():
+ for plugin_dir in sorted(
+ directory.iterdir(),
+ key=lambda path: (path.name.casefold(), path.name),
+ ):📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| for python_file in plugin_dir.glob("*.py"): | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+145
to
+156
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 隔离插件目录扫描中的文件系统异常。
这些异常会越过 建议的异常隔离方式 if source == PluginSource.THIRD_PARTY:
python_files: list[Path] = []
if directory.is_dir():
- directory_resolved = directory.resolve(strict=True)
- for plugin_dir in directory.iterdir():
+ try:
+ directory_resolved = directory.resolve(strict=True)
+ plugin_dirs = list(directory.iterdir())
+ except (FileNotFoundError, NotADirectoryError):
+ return non_default_factories, default_factories
+ except (OSError, RuntimeError) as exc:
+ self._scan_failures.append((directory, f"{type(exc).__name__}: {exc}"))
+ return non_default_factories, default_factories
+
+ for plugin_dir in plugin_dirs:
if plugin_dir.is_symlink() or not plugin_dir.is_dir():
continue
...
- for python_file in plugin_dir.glob("*.py"):
+ try:
+ python_files_in_plugin = list(plugin_dir.glob("*.py"))
+ except (OSError, RuntimeError) as exc:
+ self._scan_failures.append((plugin_dir, f"{type(exc).__name__}: {exc}"))
+ continue
+ for python_file in python_files_in_plugin:🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| if python_file.is_symlink(): | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||
| python_file_resolved = python_file.resolve(strict=True) | ||||||||||||||||||||||||||||||||||||||||||||
| except (OSError, RuntimeError): | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| if python_file_resolved != plugin_dir_resolved / python_file.name: | ||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||
| python_files.append(python_file) | ||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||
| python_files = list(directory.rglob("*.py")) | ||||||||||||||||||||||||||||||||||||||||||||
| factory_files: list[Path] = [] | ||||||||||||||||||||||||||||||||||||||||||||
| const_files: list[Path] = [] | ||||||||||||||||||||||||||||||||||||||||||||
| for f in directory.rglob("*.py"): | ||||||||||||||||||||||||||||||||||||||||||||
| for f in python_files: | ||||||||||||||||||||||||||||||||||||||||||||
| if f.stem.endswith(self._factory_module_suffix): | ||||||||||||||||||||||||||||||||||||||||||||
| factory_files.append(f) | ||||||||||||||||||||||||||||||||||||||||||||
| elif f.stem.endswith(self._const_module_suffix): | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: OneDragon-Anything/ZenlessZoneZero-OneDragon
Length of output: 22112
🏁 Script executed:
Repository: OneDragon-Anything/ZenlessZoneZero-OneDragon
Length of output: 36449
🏁 Script executed:
Repository: OneDragon-Anything/ZenlessZoneZero-OneDragon
Length of output: 371
🏁 Script executed:
Repository: OneDragon-Anything/ZenlessZoneZero-OneDragon
Length of output: 334
同步第三方插件的扫描边界。
scan_application_priorities()会将plugins目录作为third_party传入_scan_directory()。该方法仍执行directory.rglob('*_factory.py'),会报告运行时不会注册的嵌套 factory。当
source == 'third_party'时,只扫描每个插件根目录的第一层。内置目录继续使用递归扫描。🧰 Tools
🪛 Ruff (0.16.1)
[warning] 142-142: Comment contains ambiguous
;(FULLWIDTH SEMICOLON). Did you mean;(SEMICOLON)?(RUF003)
🤖 Prompt for AI Agents