feat: Plugin Power Pack — dependency declarations, analytics middleware, per-guild flags (v5.52.0)#73
Conversation
…re, per-guild flags Three structural additions that close framework gaps vs hikari/lightbulb: 1. Plugin.requires + PluginDependencyError — declare load-order requirements on Plugin subclasses; add_plugin() raises PluginDependencyError (with .missing and .plugin_class attrs) if any declared dep is not already loaded. 2. AnalyticsStore + analytics_middleware() — zero-config invocation counting; bot.use(analytics_middleware()) auto-wires the store; bot.command_stats() queries aggregate or per-guild counts. 3. Per-guild plugin feature flags — bot.disable_plugin/enable_plugin/is_plugin_enabled let server admins selectively block plugin commands; disabled commands return an ephemeral response without executing the handler; DM invocations unaffected. Exports added to easycord: PluginDependencyError, AnalyticsStore, analytics_middleware. Bump to v5.52.0. 1438 tests (35 new, including 4 end-to-end dispatch guard tests).
Reviewer's GuideImplements the Plugin Power Pack: plugin dependency declarations enforced at add-time, analytics middleware with an attached AnalyticsStore and Bot.command_stats helper, and per-guild plugin feature flags that gate command dispatch, plus exports/docs/versioning and comprehensive tests. Sequence diagram for analytics middleware wiring and statssequenceDiagram
actor Dev
participant Bot
participant EventsMixin as _EventsMixin
participant Middleware as analytics_middleware
participant Store as AnalyticsStore
Dev->>Bot: use(analytics_middleware())
Bot->>Middleware: analytics_middleware()
Middleware-->>Bot: handler (MiddlewareFn)
Note over Middleware,Store: handler._analytics_store = AnalyticsStore
Bot->>EventsMixin: use(handler)
EventsMixin->>EventsMixin: append middleware
EventsMixin->>Bot: set _analytics_store
Dev->>Bot: command_stats(guild_id)
Bot->>Store: query(guild_id)
Store-->>Bot: dict[str, int]
Bot-->>Dev: stats
Sequence diagram for per-guild plugin disable guardsequenceDiagram
participant Discord as discord.Interaction
participant Callback as callback
participant Bot
participant Registry as registry
participant Plugin as plugin
Discord->>Callback: invoke callback(...)
Callback->>Callback: build Context ctx
Callback->>Bot: is_plugin_enabled? via registry lookup
Callback->>Registry: slash_commands.get(...)
Registry-->>Callback: entry (with source)
Callback->>Bot: is_plugin_enabled(plugin_name, ctx.guild.id)
alt plugin disabled
Callback->>Callback: ctx.respond("errors.plugin_disabled", ephemeral=True)
Callback-->>Discord: end
else plugin enabled or DM
Callback->>Callback: invoke()
Callback-->>Discord: handler result
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Review limit reached
Next review available in: 8 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (14)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
PR Summary by QodoPlugin Power Pack: plugin deps, analytics middleware, per-guild plugin flags
AI Description
Diagram
High-Level Assessment
Files changed (14)
|
| """Tests for Plugin Power Pack: dependency declarations and per-guild feature flags.""" | ||
| from __future__ import annotations | ||
|
|
||
| from unittest.mock import AsyncMock, MagicMock |
| class DepA(Plugin): | ||
| pass | ||
|
|
||
| class DepB(Plugin): |
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="release_v5.52.0/notes.md" line_range="7" />
<code_context>
+
+## Overview
+
+Feature release adding the Plugin Power Pack: three structural additions that close gaps compared to hikari/lightbulb and production-grade Discord bots — safe plugin load ordering, zero-config command analytics, and per-guild plugin toggling.
+
+## New Features
</code_context>
<issue_to_address>
**nitpick (typo):** Capitalize proper names "hikari" and "lightbulb" for consistency.
Use the capitalized forms "Hikari" and "Lightbulb" (e.g., "Hikari/Lightbulb") to match the official project names and stay consistent with the rest of the documentation.
```suggestion
Feature release adding the Plugin Power Pack: three structural additions that close gaps compared to Hikari/Lightbulb and production-grade Discord bots — safe plugin load ordering, zero-config command analytics, and per-guild plugin toggling.
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
|
||
| ## Overview | ||
|
|
||
| Feature release adding the Plugin Power Pack: three structural additions that close gaps compared to hikari/lightbulb and production-grade Discord bots — safe plugin load ordering, zero-config command analytics, and per-guild plugin toggling. |
There was a problem hiding this comment.
nitpick (typo): Capitalize proper names "hikari" and "lightbulb" for consistency.
Use the capitalized forms "Hikari" and "Lightbulb" (e.g., "Hikari/Lightbulb") to match the official project names and stay consistent with the rest of the documentation.
| Feature release adding the Plugin Power Pack: three structural additions that close gaps compared to hikari/lightbulb and production-grade Discord bots — safe plugin load ordering, zero-config command analytics, and per-guild plugin toggling. | |
| Feature release adding the Plugin Power Pack: three structural additions that close gaps compared to Hikari/Lightbulb and production-grade Discord bots — safe plugin load ordering, zero-config command analytics, and per-guild plugin toggling. |
Code Review by Qodo
Context used✅ Compliance rules (platform):
36 rules 1. Per-guild state stored in-memory
|
| self._analytics_store: AnalyticsStore | None = None | ||
| self._guild_disabled_plugins: dict[int, set[str]] = {} |
There was a problem hiding this comment.
1. Per-guild state stored in-memory 📘 Rule violation ⌂ Architecture
The PR introduces per-guild feature-flag state and per-guild analytics counters stored on the Bot instance (_guild_disabled_plugins, _analytics_store) without any database persistence. This violates the requirement that long-lived per-guild state must be persisted in the database rather than held in-process on the bot.
Agent Prompt
## Issue description
Per-guild feature flags (`_guild_disabled_plugins`) and per-guild analytics counters (`_analytics_store` / `AnalyticsStore.counts`) are stored only in memory on the `Bot` instance, so they are lost on restart and violate the compliance rule requiring DB-backed per-guild state.
## Issue Context
The PR explicitly implements per-guild plugin enable/disable and per-guild command stats. These are durable, guild-scoped data structures keyed by `guild_id`, and therefore must be persisted via the project’s database layer (repository/DAO or GuildRecord-style row), not as a Python dict on the bot.
## Fix Focus Areas
- easycord/bot.py[118-119]
- easycord/_bot_plugins.py[378-408]
- easycord/_bot_events.py[74-93]
- easycord/middleware.py[330-388]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 95daab42ba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| _entry = ( | ||
| _registry.slash_commands.get(f"global:{_cmd}") | ||
| or _registry.slash_commands.get(f"guild:{ctx.guild.id}:{_cmd}") | ||
| ) |
There was a problem hiding this comment.
Resolve the invoked slash entry before checking flags
When the invoked command is not a top-level global slash command, this lookup can resolve the wrong registry entry or none at all: grouped slash commands are registered as global:<group> <subcommand> while _cmd is only the subcommand name, and a guild-specific command with the same name as a global command is shadowed because the global key is checked first. In those cases, disabling the plugin for that guild does not reliably block the command that actually ran.
Useful? React with 👍 / 👎.
| if ephemeral: | ||
| ctx._force_ephemeral = True | ||
|
|
||
| if ctx.guild is not None: |
There was a problem hiding this comment.
Apply plugin flags to context-menu callbacks
This guard only runs in build_slash_callback, so plugins that expose @user_command or @message_command still execute their context-menu commands after bot.disable_plugin(name, guild_id) for the same guild. Since these are registered plugin commands too, disabling a plugin leaves part of its user-facing command surface active.
Useful? React with 👍 / 👎.
Summary
Three structural additions that close framework gaps vs hikari/lightbulb and production-grade Discord bots:
Plugin.requires = ("economy",)on a subclass;bot.add_plugin()raisesPluginDependencyError(with.missingand.plugin_class) if any declared dep isn't loaded yetbot.use(analytics_middleware())auto-wires anAnalyticsStore;bot.command_stats(guild_id=None)returns aggregate or per-guild invocation counts with zero additional codebot.disable_plugin(name, guild_id)/enable_plugin/is_plugin_enabled; disabled commands return an ephemeral block without executing the handler; DM invocations are always unaffectedNew public exports from
easycord:PluginDependencyError,AnalyticsStore,analytics_middleware.Files changed
easycord/plugin.pyrequires: tuple[str, ...]class attributeeasycord/_bot_plugins.pyPluginDependencyError, dependency check inadd_plugin(), three feature-flag methodseasycord/middleware.pyAnalyticsStoredataclass +analytics_middleware()factoryeasycord/_bot_events.pyuse()easycord/bot.py_analytics_store,_guild_disabled_pluginsinit vars +command_stats()methodeasycord/_command_callbacks.pyeasycord/__init__.pyPluginDependencyError,AnalyticsStore,analytics_middlewaretests/test_plugin_power_pack.pytests/test_middleware.pyAnalyticsStoreandanalytics_middlewareCHANGELOG.md,pyproject.toml,easycord/__init__.py,README.md,docs/getting-started.mdrelease_v5.52.0/notes.mdTest plan
ruff check --select E9,F63,F7,F82 easycord tests— passes (blocking lint gate)pytest tests/ -q— 1438 passed (up from 1335; 35 new tests)python scripts/check_release_metadata.py— passespython scripts/verify_plugin_tests.py— passestests/test_plugin_power_pack.py::TestPerGuildFlagDispatchGuard— 4 integration tests exercise the full dispatch path with realBot+invoke():Summary by Sourcery
Add plugin dependency declarations, analytics middleware with a shared analytics store, and per-guild plugin enable/disable flags, and release them as EasyCord v5.52.0.
New Features:
Enhancements:
Build:
Documentation:
Tests: