Python 3.10+. MIT license. Contributions welcome.
This file covers standards and conventions. For the end-to-end workflows — fixing a bug, adding a feature, shipping a release — see the Development Workflow guide.
git clone https://github.com/rolling-codes/EasyCord.git
cd EasyCord
pip install -e ".[dev]"
easycord doctorpip install -e ".[dev]" installs pytest, pytest-asyncio, build, twine, and deep-translator alongside the package in editable mode. easycord doctor confirms your local setup is wired correctly before you write anything.
pytest tests/ # full suite
pytest tests/test_middleware.py -v # single file
pytest tests/test_middleware.py::test_name -v # single testasyncio_mode = "auto" is set in pyproject.toml — no manual event loop setup needed in test files.
Coverage must not drop below 80%. All new code requires tests and must not be merged without them.
We enforce minimum test counts for plugins using scripts/verify_plugin_tests.py (counts test_* functions per plugin).
- Complex Plugins (AIModerator, Tickets, Birthday, Levels, Reminders): ≥ 20 tests.
- Simple Plugins (Suggestions, Tags, Starboard): ≥ 20 tests.
You can validate these thresholds locally by running:
python scripts/verify_plugin_tests.py- Python 3.10+ type hints on all public functions and methods
- Immutable patterns — return new objects, never mutate in place
- Functions under 50 lines; files under 800 lines
- No hardcoded strings in plugins — use
ctx.t(...)and locale files - No
# type: ignorewithout a specific error code (e.g.# type: ignore[assignment]) - No
print()or bareexcept:blocks in production code
ctx.user/ctx.member— correct.ctx.author— does not exist.ctx.is_adminis a property, not a method. Never callctx.is_admin().ToolLimitermethods (check_limit,reset_user,reset_tool) are async — alwaysawaitthem.@ai_toolrequires an explicitToolSafetyannotation to register.- Before calling
.send()on a channel fromctxor Discord, narrow the type first:from easycord.helpers.tools import SENDABLE_CHANNEL_TYPES if isinstance(channel, SENDABLE_CHANNEL_TYPES): await channel.send(...)
Construct plugins with __new__ and set _bot directly — do not assign to the bot property:
plugin = MyPlugin.__new__(MyPlugin)
plugin._bot = bot
Plugin.__init__(plugin)For commands, prefer invoke over constructing FakeContext by hand:
from easycord.testing import invoke
ctx = await invoke(bot, "ping")
ctx.assert_content("Pong!")When you need locale, roles, or DM context, use FakeContextBuilder:
from easycord.testing import FakeContextBuilder
ctx = (
FakeContextBuilder()
.with_user(42, name="alice")
.in_guild(100)
.as_admin()
.with_roles(999)
.build()
)- Branch from
main. Naming:feat/description,fix/description,docs/description. - Write tests first. Coverage must not drop.
- Run
pytest tests/— all tests must pass. - Run
python scripts/check_release_metadata.py— catches version drift acrosspyproject.toml,__init__.py, andCHANGELOG.md. - PR title follows conventional commits:
feat:,fix:,docs:,chore:, etc. - PR body: what changed, why, and a short test plan.
- One reviewer required before merge.
<type>: <description>
<optional body>
Types: feat, fix, refactor, docs, test, chore, perf, ci
| Label | Meaning |
|---|---|
bug |
Confirmed broken behavior |
enhancement |
New feature or improvement |
docs |
Documentation only |
good first issue |
Well-scoped for new contributors |
breaking |
Changes the public API |
If your PR adds a plugin or changes plugin behavior:
- Update
docs/plugin-authoring.mdif the authoring surface changed - Add an example under
examples/if the feature is non-obvious - Per-guild state belongs in the database layer, not on
self - Never hardcode response strings — use
ctx.t(...)with locale keys
New plugins can be scaffolded with easycord plugin create and validated with easycord plugin check.
Never commit API keys, tokens, or credentials. All secrets must come from environment variables. If you discover a security issue, open a private GitHub security advisory rather than a public issue.
- GitHub Issues for bugs and feature requests
easycord doctorfor local environment diagnosticseasycord inspectandeasycord sync-planfor command registration debuggingcontext/architecture.mdandcontext/conventions.mdfor deeper framework internals