showrunner 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- showrunner/__init__.py +12 -0
- showrunner/app.py +138 -0
- showrunner/cli.py +896 -0
- showrunner/config.py +246 -0
- showrunner/database.py +75 -0
- showrunner/hookspecs.py +129 -0
- showrunner/models.py +169 -0
- showrunner/pdf_layout.toml +138 -0
- showrunner/plugins/__init__.py +38 -0
- showrunner/plugins/admin.py +208 -0
- showrunner/plugins/cmd.py +51 -0
- showrunner/plugins/comms.py +51 -0
- showrunner/plugins/dashboard.py +72 -0
- showrunner/plugins/db.py +182 -0
- showrunner/plugins/designer.py +51 -0
- showrunner/plugins/lighter.py +51 -0
- showrunner/plugins/mixer.py +51 -0
- showrunner/plugins/printer.py +600 -0
- showrunner/plugins/programmer.py +51 -0
- showrunner/plugins/prompter.py +51 -0
- showrunner/plugins/recorder.py +51 -0
- showrunner/plugins/scripter.py +1076 -0
- showrunner/plugins/stage_manager.py +51 -0
- showrunner/plugins/stopper.py +51 -0
- showrunner/templates/sqladmin/layout.html +80 -0
- showrunner/ui.py +191 -0
- showrunner-0.1.0.dist-info/METADATA +112 -0
- showrunner-0.1.0.dist-info/RECORD +30 -0
- showrunner-0.1.0.dist-info/WHEEL +4 -0
- showrunner-0.1.0.dist-info/entry_points.txt +3 -0
showrunner/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""ShowRunner - A collection of tools for live performances."""
|
|
2
|
+
|
|
3
|
+
import pluggy
|
|
4
|
+
|
|
5
|
+
# hookimpl must be defined before importing ShowRunner (which transitively
|
|
6
|
+
# imports plugins that do ``import showrunner; showrunner.hookimpl``).
|
|
7
|
+
hookimpl = pluggy.HookimplMarker("showrunner")
|
|
8
|
+
"""Marker to be used in plugin hook implementations."""
|
|
9
|
+
|
|
10
|
+
from .app import ShowRunner # noqa: E402
|
|
11
|
+
|
|
12
|
+
__all__ = ["hookimpl", "ShowRunner"]
|
showrunner/app.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""ShowRunner core application.
|
|
2
|
+
|
|
3
|
+
Manages the plugin lifecycle and wires plugins into the FastAPI application.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
import logging
|
|
10
|
+
from importlib.metadata import entry_points
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import pluggy
|
|
14
|
+
from fastapi import FastAPI
|
|
15
|
+
|
|
16
|
+
from . import plugins
|
|
17
|
+
from .config import ConfigWatcher, ShowRunnerConfig, load_config
|
|
18
|
+
from .hookspecs import ShowRunnerSpec
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_plugin_manager(
|
|
24
|
+
config: ShowRunnerConfig | None = None,
|
|
25
|
+
) -> pluggy.PluginManager:
|
|
26
|
+
"""Create and configure a PluginManager with built-in plugins loaded.
|
|
27
|
+
|
|
28
|
+
Plugins whose name (case-insensitive) appears in
|
|
29
|
+
``config.plugins.disabled`` are skipped.
|
|
30
|
+
"""
|
|
31
|
+
pm = pluggy.PluginManager("showrunner")
|
|
32
|
+
pm.add_hookspecs(ShowRunnerSpec)
|
|
33
|
+
|
|
34
|
+
disabled = set()
|
|
35
|
+
if config is not None:
|
|
36
|
+
disabled = {n.lower() for n in config.plugins.disabled}
|
|
37
|
+
|
|
38
|
+
# Register all built-in plugins
|
|
39
|
+
for plugin_class in plugins.get_builtin_plugins():
|
|
40
|
+
name = plugin_class.__name__.lower()
|
|
41
|
+
if name in disabled:
|
|
42
|
+
logger.info("Skipping disabled plugin %s", plugin_class.__name__)
|
|
43
|
+
continue
|
|
44
|
+
pm.register(plugin_class())
|
|
45
|
+
|
|
46
|
+
# Discover external plugins installed via setuptools entry points.
|
|
47
|
+
# We load them manually (instead of pm.load_setuptools_entrypoints)
|
|
48
|
+
# so that entry points pointing to a class are instantiated first —
|
|
49
|
+
# pluggy requires hook methods to be bound (i.e. on an instance).
|
|
50
|
+
for ep in entry_points(group="showrunner"):
|
|
51
|
+
if pm.get_plugin(ep.name) is not None:
|
|
52
|
+
continue
|
|
53
|
+
plugin = ep.load()
|
|
54
|
+
if inspect.isclass(plugin):
|
|
55
|
+
plugin = plugin()
|
|
56
|
+
name = getattr(plugin, "__name__", type(plugin).__name__).lower()
|
|
57
|
+
if name in disabled:
|
|
58
|
+
logger.info("Skipping disabled external plugin %s", name)
|
|
59
|
+
continue
|
|
60
|
+
pm.register(plugin, name=ep.name)
|
|
61
|
+
|
|
62
|
+
return pm
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ShowRunner:
|
|
66
|
+
"""Main ShowRunner application.
|
|
67
|
+
|
|
68
|
+
Holds the plugin manager and the FastAPI app instance.
|
|
69
|
+
Provides lifecycle management for all registered plugins.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, config_path: Path | None = None) -> None:
|
|
73
|
+
self.config = load_config(config_path)
|
|
74
|
+
self._config_path = config_path or self.config._source_path
|
|
75
|
+
|
|
76
|
+
# Configure logging from show.toml unless already set by the CLI.
|
|
77
|
+
# basicConfig is a no-op when handlers already exist, so CLI
|
|
78
|
+
# flags (--log-level, --verbose, --quiet) take precedence.
|
|
79
|
+
logging.basicConfig(
|
|
80
|
+
level=self.config.logging.level.upper(),
|
|
81
|
+
format="%(levelname)-8s %(name)s: %(message)s",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self.pm = get_plugin_manager(self.config)
|
|
85
|
+
self._config_watcher: ConfigWatcher | None = None
|
|
86
|
+
self.api = FastAPI(title="ShowRunner", version="0.1.0", docs_url="/api")
|
|
87
|
+
self._mount_routes()
|
|
88
|
+
|
|
89
|
+
def _mount_routes(self) -> None:
|
|
90
|
+
"""Collect APIRouters from all plugins and include them in the FastAPI app."""
|
|
91
|
+
routers = self.pm.hook.showrunner_get_routes()
|
|
92
|
+
for router in routers:
|
|
93
|
+
if router is not None:
|
|
94
|
+
self.api.include_router(router)
|
|
95
|
+
|
|
96
|
+
def startup(self) -> None:
|
|
97
|
+
"""Invoke the startup hook on all plugins and start the config watcher."""
|
|
98
|
+
self.pm.hook.showrunner_startup(app=self)
|
|
99
|
+
|
|
100
|
+
if self._config_path is not None:
|
|
101
|
+
self._config_watcher = ConfigWatcher(self._config_path, self)
|
|
102
|
+
self._config_watcher.start()
|
|
103
|
+
logger.info("Watching %s for changes", self._config_path)
|
|
104
|
+
print(f" Config: {self._config_path} (watching for changes)")
|
|
105
|
+
|
|
106
|
+
def shutdown(self) -> None:
|
|
107
|
+
"""Invoke the shutdown hook on all plugins and stop the config watcher."""
|
|
108
|
+
if self._config_watcher is not None:
|
|
109
|
+
self._config_watcher.stop()
|
|
110
|
+
self._config_watcher = None
|
|
111
|
+
self.pm.hook.showrunner_shutdown(app=self)
|
|
112
|
+
|
|
113
|
+
def list_plugins(self) -> list[dict]:
|
|
114
|
+
"""Return metadata from all registered plugins."""
|
|
115
|
+
return self.pm.hook.showrunner_register()
|
|
116
|
+
|
|
117
|
+
def list_commands(self) -> list[dict]:
|
|
118
|
+
"""Collect CLI commands from all plugins."""
|
|
119
|
+
results = self.pm.hook.showrunner_get_commands()
|
|
120
|
+
# Flatten the list-of-lists into a single list
|
|
121
|
+
return [cmd for group in results if group for cmd in group]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _create_app() -> FastAPI:
|
|
125
|
+
"""Create and start a ShowRunner instance, returning its FastAPI app.
|
|
126
|
+
|
|
127
|
+
Used as the ASGI entry point for ``uvicorn showrunner.app:app --reload``.
|
|
128
|
+
"""
|
|
129
|
+
show = ShowRunner()
|
|
130
|
+
show.startup()
|
|
131
|
+
return show.api
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def __getattr__(name: str):
|
|
135
|
+
if name == "app":
|
|
136
|
+
globals()["app"] = _create_app()
|
|
137
|
+
return globals()["app"]
|
|
138
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|