docs: fix README_NBA_LOGOS and PLUGIN_CONFIGURATION_GUIDE

scripts/README_NBA_LOGOS.md
- "python download_nba_logos.py" — wrong on two counts. The script
  is at scripts/download_nba_logos.py (not the project root), and
  "python" is Python 2 on most systems. Replaced all 4 occurrences
  with "python3 scripts/download_nba_logos.py".
- The doc framed itself as the way to set up "the NBA leaderboard".
  The basketball/leaderboard functionality is now in the
  basketball-scoreboard and ledmatrix-leaderboard plugins (in the
  ledmatrix-plugins repo), which auto-download logos on first run.
  Reframed the script as a pre-population utility for offline / dev
  use cases.
- Bumped the documented Python minimum from 3.7 to 3.9 to match
  the rest of the project.

docs/PLUGIN_CONFIGURATION_GUIDE.md
- The "Plugin Manifest" example was missing 3 fields the plugin
  loader actually requires: id, entry_point, and class_name. A
  contributor copying this manifest verbatim would get
  PluginError("No class_name in manifest") at load time — the same
  loader bug already found in stock-news. Added all three.
- The same example showed config_schema as an inline object. The
  loader expects config_schema to be a file path string (e.g.
  "config_schema.json") with the actual schema in a separate JSON
  file — verified earlier in this audit. Fixed.
- Added a paragraph explaining the loader's required fields and
  the case-sensitivity rule on class_name (the bug that broke
  hello-world's manifest before this PR fixed it).
- "Plugin Manager Class" example had the wrong constructor
  signature: (config, display_manager, cache_manager, font_manager).
  The real BasePlugin.__init__ at base_plugin.py:53-60 takes
  (plugin_id, config, display_manager, cache_manager, plugin_manager).
  A copy-pasted example would TypeError on instantiation. Fixed,
  including a comment noting which attributes BasePlugin sets up.
- Renamed the example class from MyPluginManager to MyPlugin to
  match the project convention (XxxPlugin / XxxScoreboardPlugin
  in actual plugins).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-04-07 14:07:39 -04:00
parent 33d023bbd5
commit 40fcd1ed9f
2 changed files with 48 additions and 27 deletions

View File

@@ -184,37 +184,45 @@ plugin-repos/
```json
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "Plugin description",
"author": "Your Name",
"entry_point": "manager.py",
"class_name": "MyPlugin",
"display_modes": ["my_plugin"],
"config_schema": {
"type": "object",
"properties": {
"enabled": {"type": "boolean", "default": false},
"update_interval": {"type": "integer", "default": 3600}
}
}
"config_schema": "config_schema.json"
}
```
The required fields the plugin loader will check for are `id`,
`name`, `version`, `class_name`, and `display_modes`. `entry_point`
defaults to `manager.py` if omitted. `config_schema` must be a
**file path** (relative to the plugin directory) — the schema itself
lives in a separate JSON file, not inline in the manifest. The
`class_name` value must match the actual class defined in the entry
point file **exactly** (case-sensitive, no spaces); otherwise the
loader fails with `AttributeError` at load time.
### Plugin Manager Class
```python
from src.plugin_system.base_plugin import BasePlugin
class MyPluginManager(BasePlugin):
def __init__(self, config, display_manager, cache_manager, font_manager):
super().__init__(config, display_manager, cache_manager, font_manager)
self.enabled = config.get('enabled', False)
class MyPlugin(BasePlugin):
def __init__(self, plugin_id, config, display_manager, cache_manager, plugin_manager):
super().__init__(plugin_id, config, display_manager, cache_manager, plugin_manager)
# self.config, self.display_manager, self.cache_manager,
# self.plugin_manager, self.logger, and self.enabled are
# all set up by BasePlugin.__init__.
def update(self):
"""Update plugin data"""
"""Fetch/update data. Called based on update_interval."""
pass
def display(self, force_clear=False):
"""Display plugin content"""
"""Render plugin content to the LED matrix."""
pass
def get_duration(self):