Add auto-discovered server item type plugins
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
from typing import Callable, Protocol
|
||||
|
||||
from ..item_types import ItemUseResult
|
||||
from ..models import WorldItem
|
||||
|
||||
from . import clock, dice, piano, radio, wheel, widget
|
||||
|
||||
|
||||
class ItemModule(Protocol):
|
||||
"""Shape required by item modules consumed by catalog/handlers."""
|
||||
@@ -29,13 +30,56 @@ class ItemModule(Protocol):
|
||||
use_item: Callable[[WorldItem, str, Callable[[dict], str]], ItemUseResult]
|
||||
|
||||
|
||||
ITEM_TYPE_ORDER: tuple[str, ...] = ("clock", "dice", "piano", "radio_station", "wheel", "widget")
|
||||
@dataclass(frozen=True)
|
||||
class ItemTypePlugin:
|
||||
"""Runtime-loaded item type plugin metadata."""
|
||||
|
||||
ITEM_MODULES: dict[str, ItemModule] = {
|
||||
"clock": clock,
|
||||
"dice": dice,
|
||||
"piano": piano,
|
||||
"radio_station": radio,
|
||||
"wheel": wheel,
|
||||
"widget": widget,
|
||||
}
|
||||
type: str
|
||||
order: int
|
||||
module: ItemModule
|
||||
|
||||
|
||||
def _load_item_type_plugins() -> list[ItemTypePlugin]:
|
||||
"""Discover and load item-type plugins from `items/types/*/plugin.py`."""
|
||||
|
||||
base_dir = Path(__file__).resolve().parent / "types"
|
||||
plugins: list[ItemTypePlugin] = []
|
||||
if not base_dir.exists():
|
||||
raise RuntimeError(f"item type plugin directory missing: {base_dir}")
|
||||
|
||||
for entry in sorted(base_dir.iterdir(), key=lambda path: path.name):
|
||||
if not entry.is_dir():
|
||||
continue
|
||||
if entry.name.startswith("__"):
|
||||
continue
|
||||
plugin_module = import_module(f"{__package__}.types.{entry.name}.plugin")
|
||||
raw_plugin = getattr(plugin_module, "ITEM_TYPE_PLUGIN", None)
|
||||
if not isinstance(raw_plugin, dict):
|
||||
raise RuntimeError(f"invalid ITEM_TYPE_PLUGIN in {plugin_module.__name__}")
|
||||
type_id = raw_plugin.get("type")
|
||||
order = raw_plugin.get("order")
|
||||
module = raw_plugin.get("module")
|
||||
if not isinstance(type_id, str) or not type_id.strip():
|
||||
raise RuntimeError(f"plugin {plugin_module.__name__} missing string 'type'")
|
||||
if not isinstance(order, int):
|
||||
raise RuntimeError(f"plugin {plugin_module.__name__} missing int 'order'")
|
||||
if module is None:
|
||||
raise RuntimeError(f"plugin {plugin_module.__name__} missing 'module'")
|
||||
plugins.append(ItemTypePlugin(type=type_id.strip(), order=order, module=module))
|
||||
|
||||
if not plugins:
|
||||
raise RuntimeError("no item type plugins discovered")
|
||||
|
||||
seen: set[str] = set()
|
||||
for plugin in plugins:
|
||||
if plugin.type in seen:
|
||||
raise RuntimeError(f"duplicate item type plugin registered: {plugin.type}")
|
||||
seen.add(plugin.type)
|
||||
|
||||
plugins.sort(key=lambda plugin: (plugin.order, plugin.type))
|
||||
return plugins
|
||||
|
||||
|
||||
ITEM_PLUGINS: tuple[ItemTypePlugin, ...] = tuple(_load_item_type_plugins())
|
||||
ITEM_TYPE_ORDER: tuple[str, ...] = tuple(plugin.type for plugin in ITEM_PLUGINS)
|
||||
ITEM_MODULES: dict[str, ItemModule] = {plugin.type: plugin.module for plugin in ITEM_PLUGINS}
|
||||
|
||||
1
server/app/items/types/__init__.py
Normal file
1
server/app/items/types/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Item type plugin package."""
|
||||
1
server/app/items/types/clock/__init__.py
Normal file
1
server/app/items/types/clock/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Item type plugin package."""
|
||||
11
server/app/items/types/clock/plugin.py
Normal file
11
server/app/items/types/clock/plugin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Plugin registration for clock item type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import clock
|
||||
|
||||
ITEM_TYPE_PLUGIN = {
|
||||
"type": "clock",
|
||||
"order": 10,
|
||||
"module": clock,
|
||||
}
|
||||
1
server/app/items/types/dice/__init__.py
Normal file
1
server/app/items/types/dice/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Item type plugin package."""
|
||||
11
server/app/items/types/dice/plugin.py
Normal file
11
server/app/items/types/dice/plugin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Plugin registration for dice item type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import dice
|
||||
|
||||
ITEM_TYPE_PLUGIN = {
|
||||
"type": "dice",
|
||||
"order": 20,
|
||||
"module": dice,
|
||||
}
|
||||
1
server/app/items/types/piano/__init__.py
Normal file
1
server/app/items/types/piano/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Item type plugin package."""
|
||||
11
server/app/items/types/piano/plugin.py
Normal file
11
server/app/items/types/piano/plugin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Plugin registration for piano item type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import piano
|
||||
|
||||
ITEM_TYPE_PLUGIN = {
|
||||
"type": "piano",
|
||||
"order": 30,
|
||||
"module": piano,
|
||||
}
|
||||
1
server/app/items/types/radio_station/__init__.py
Normal file
1
server/app/items/types/radio_station/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Item type plugin package."""
|
||||
11
server/app/items/types/radio_station/plugin.py
Normal file
11
server/app/items/types/radio_station/plugin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Plugin registration for radio_station item type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import radio
|
||||
|
||||
ITEM_TYPE_PLUGIN = {
|
||||
"type": "radio_station",
|
||||
"order": 40,
|
||||
"module": radio,
|
||||
}
|
||||
1
server/app/items/types/wheel/__init__.py
Normal file
1
server/app/items/types/wheel/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Item type plugin package."""
|
||||
11
server/app/items/types/wheel/plugin.py
Normal file
11
server/app/items/types/wheel/plugin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Plugin registration for wheel item type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import wheel
|
||||
|
||||
ITEM_TYPE_PLUGIN = {
|
||||
"type": "wheel",
|
||||
"order": 50,
|
||||
"module": wheel,
|
||||
}
|
||||
1
server/app/items/types/widget/__init__.py
Normal file
1
server/app/items/types/widget/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Item type plugin package."""
|
||||
11
server/app/items/types/widget/plugin.py
Normal file
11
server/app/items/types/widget/plugin.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""Plugin registration for widget item type."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import widget
|
||||
|
||||
ITEM_TYPE_PLUGIN = {
|
||||
"type": "widget",
|
||||
"order": 60,
|
||||
"module": widget,
|
||||
}
|
||||
Reference in New Issue
Block a user