User:Eduralph/Sandbox/Gramps 6.0 Wiki Manual - Addon Development - Rules
← Previous · Index · Next →
Contents
Overview
Normative reference for addon authors. Conceptual / how-to material lives in the other section pages; this page enumerates the guidelines and is the one to cite in code review.
Repository scope
- This page applies to the addon repository —
gramps-project/addons-source. It does not govern Gramps core. - Core contributions (
gramps-project/gramps) follow the separate Core Development — Rules page. The two repositories diverge on branch target, test layout, translation tooling, and which static checks are enforced — do not transfer a rule across without checking it here. - The full Python coding standard is inherited from core's
../gramps/AGENTS.md; this page restates the parts addon code review enforces and adds the addon-specific structure, packaging, and translation rules that live outside that file. - When in doubt, the authoritative source wins and is what to check. These pages are a convenience restatement. On coding style, core's
../gramps/AGENTS.mdis the source of truth; on addon-specific rules, the authority is upstreamaddons-source(itsCONTRIBUTING.mdand a maintainer's ruling on the PR). Where this page is silent, ambiguous, or disagrees with the authoritative source on the target branch, that source wins — verify against it rather than relying on this page from memory. - Core stands in where this page doesn't — one way only. Where this page is not specific or prescriptive on a point, the Core Development — Rules page (and core's
AGENTS.md) is the default that fills the gap — addons inherit from core. The fallback runs in this direction only: where this page is prescriptive on an addon-specific concern (structure, packaging, branch target, test layout —tests/+test_*.py,maintenance/gramps60), it governs and core does not override it; and the addon guidelines never fill a gap in the core page.
Conventions
RFC 2119 keywords, with our short forms:
| Keyword | Meaning |
|---|---|
| MUST / MUST NOT | Required; a violation is a defect |
| SHOULD / SHOULD NOT | Strongly recommended; deviate only with a stated reason |
| MAY | Allowed |
Where a rule has a known origin — an upstream PR, a maintainer ruling, a Mantis bug — it's cited inline so the rule is auditable.
Structure
- MUST: the addon's folder name is a valid Python import name (an importable identifier — no spaces). Gramps puts each addon's directory on
sys.pathand addons share code viaimport <FolderName>(see 04 - Technical Documentation/addons-development.md → "name your addons with a name appropriate for Python imports"). The folder name need not match theidin.gpr.py: the registrationidis an independent plugin key and routinely differs (e.g. folderDeepConnectionsGramplet↔ idDeep Connections Gramplet), and one folder may register several plugins with unrelated ids. - MUST:
.gpr.pydeclaresgramps_target_versionmatching the Gramps minor the addon targets. - MUST:
fnamepoints to an implementation module shipped in the same folder. - MUST: the addon is physically present under the plugin path — a physical copy works on every Gramps version and OS. (Gramps 6.1+ also discovers an addon reached via a symlink, but a physical copy is the portable default.)
- MUST NOT: import
register,GRAMPLET,STABLE,_, or any other name Gramps injects into the.gpr.pynamespace. - MUST NOT: add
__init__.pyto the addon directory itself. The plugin loader puts the addon dir onsys.pathand imports<Addon>.pyby name; making the addon dir a regular package disturbs that resolution and can trigger the Mantis 12691 submodule-binding trap. (See 08-testing → Why `tests/__init__.py` exists.) - MUST (
TOOLkind): register anoptionclasseven when the tool takes no options. Gramps refuses to load aTOOLwithout one; an emptytool.ToolOptionssubclass is sufficient. - SHOULD: ship a
po/directory with at leasttemplate.potif any user-visible string exists. Generate it withmake.py init <Addon>(see 13-packaging); if it's missing the maintainer creates it on initial check-in. - MAY: ship a
tests/package with an__init__.pymarker and at least one test — most existing addons predate addon unit tests. When tests are shipped, the__init__.pymarker keeps dotted-path loading deterministic and the layout rules under Testing apply; a bug fix still SHOULD ship a regression test. - MAY: ship multiple plugin kinds from a single addon — multiple
register(...)calls in one.gpr.py, and/or multiple.gpr.pyfiles in the addon folder (the loader scans every*.gpr.py).
Source location
- MUST: edit addon source in
addons-source/, never in the live plugin directory. The auto-sync runs source → installed plugin one-way; edits in the live dir are silently overwritten on the next source save.
Translation
The full how-to (registration setup, make.py lifecycle, Glade runtime-override pattern, function reference) lives in 12-internationalization. The rules below are what code review enforces.
- MUST: wrap every user-visible string with
_(). - MUST NOT:
import _in.gpr.py— Gramps' plugin loader injects it. Implementation modules MUST bind it explicitly via_ = glocale.get_addon_translator(__file__).gettext. - MUST NOT: wrap an f-string or
.format()result in a translation function.xgettextcannot extract dynamically built strings.- Bad:
_(f"User {name}"),_("User {}".format(name)) - Good:
_("User %s") % name
- Bad:
- MUST (Glade): translatable strings in
.glade/.uifiles are not picked up by the addon translation tooling — the extractor only sees Python. For each translatable Glade string, give the widget a meaningfulid, mark the string withtranslatable="yes"(optionally with a"context|"prefix), and override the label at runtime in Python:self.get_widget("place_name_label").set_label(_("place|Name:")). - SHOULD: use
ngettext(singular, plural, n)for plural forms. - SHOULD: use the pipe-prefix form
_("Context|String")whenever a word could carry multiple senses (e.g._("book|Title")vs_("person|Title")). This is the convention used throughoutaddons-sourceand is what translators see in the.pofile. The two-arg form_(msg, context)works equivalently. MUST NOT callpgettextorsgettextdirectly — go through_. - SHOULD: use
N_("…")to mark a string for extraction without translating it at call time (e.g. for module-level constants that are translated later when displayed).
Addons have no
POTFILES.into maintain by hand — the per-addonpo/template.potis regenerated bymake.py init <Addon>(see 13-packaging). Maintainingpo/POTFILES.in/POTFILES.skipis a core rule; see the Core Development — Rules page.
Runtime
- MUST: perform every database write inside a
DbTxn:with DbTxn(_("Adding example"), db) as trans: db.add_person(person, trans) - MUST: declare runtime imports in
requires_modusing the importable module name (PIL), not the PyPI distribution name (Pillow). - MUST: verify each
requires_modentry withimportlib.util.find_spec("<name>")on a system with the package installed before publishing. - MUST: use
requires_gifor GObject-Introspection bindings, with version strings. The version pin must match what the code actually imports at runtime — pins can drift between Gramps minors (e.g. GExiv2 handling was rewritten onmaintenance/gramps61per addons-source PR 829), so verify the pin against the target branch's related code, not just the previous branch's working declaration. - SHOULD: use handles (
PersonHandle, etc.) for internal traversal; reserve Gramps IDs (I0001, …) for user-facing display. Handles are internal and stable; Gramps IDs are user-editable and rewritten in bulk by the Reorder Gramps IDs tool. - SHOULD: import only from
gramps.gen.*.gramps.gui.*andgramps.plugins.*are internal to the shipped distribution and break across Gramps versions. - SHOULD: use a module-level logger (
LOG = logging.getLogger(__name__)); MUST NOT useprint()for diagnostic output. - SHOULD: raise existing exceptions from
gramps.gen.errorsandgramps.gen.db.exceptionsbefore inventing a new class. - SHOULD: raise
HandleErrorfor invalid or missing handles. - SHOULD: compare backlink class names by string.
db.find_backlink_handles(handle)yields(class_name, handle)tuples whereclass_nameis"Person"/"Family"/ … as astr, not the Python class —if cls is Person:always evaluatesFalse. - MAY: introduce a new exception class only when none of the existing ones accurately represent the error condition.
Testing
MUST: use stdlib
unittest— neverpytest. Gramps itself standardises onunittest, which keeps addon tests contributable upstream.MUST: name test files
test_*.pyand place them in atests/package alongside the addon module.MUST: scope platform-specific tests with the correct prefix:
Prefix Where it runs test_*.pyAll platforms test_linux_*.pyLinux only test_windows_*.pyWindows only test_integration_*.pyLinux only — full-pipeline / DB-backed MUST: tests run cleanly without the addon's
requires_moddependencies installed in the Python that runs them — mock at the import boundary, or skip cleanly with@unittest.skipUnless(...). Mac contributors can't easily install addon deps into the Gramps Python, and there's no Gramps debug-mode on Mac. (Gary Griffin, 2026-05-16.)SHOULD: ship a regression test with every bug fix that fails pre-fix and passes post-fix. Doc-only PRs are the only exception.
SHOULD: prefer
example.gramps-backed tests over mocked DBs for DB-traversal logic — real data has cross-typed backlinks and ID-normalisation shapes that mocks don't reproduce.MAY: ship mocked unit tests alongside real-DB tests as complementary coverage.
Coding style
The coding standard is core's ../gramps/AGENTS.md, in full — this section lists only the addon deltas. Black, Python 3.10+ type hints (X | None, list[X]), Sphinx docstrings, import grouping with comment headers, class-header navigation comments, the cb_ callback prefix, handle/ID types from gramps.gen.types — all are specified there and apply to addon Python unchanged. They are not restated below; anything this section is silent on follows core. The deltas are only these:
- Enforcement is advisory, so the core standard's coding MUSTs read as SHOULDs here. addons-source runs no
black/mypy/ pylint gate — the reviewer weighs the standard; CI does not block on it. You SHOULD still runblack --checkbefore pushing, so the maintainer's cherry-pick forward to gramps61 stays clean. - Two rules are not softened — they stay MUST despite the lighter gate: every new
.pyfile carries a GPL-2.0-or-later license header with copyright, and every user-visible string is wrapped with_()(§Translation). gen-self-containment, reframed. Core's MUST thatgramps.gen.*import no other submodule has no direct addon analog, but addon code SHOULD uphold the same discipline against itself: factor pure logic into modules that don't importgramps.gui.*, so it stays unit-testable without a display.
Contributor workflow
- MUST: one logical fix per PR. Bundling hides mistakes.
- MUST: target the right branch — addon changes (
addons-source) →maintenance/gramps60. The maintainer cherry-picks forward togramps61. (Gary Griffin on addons-source PR 915, 2026-05-24.) A reviewer's instruction on a specific PR wins over the default targeting. (e.g. Nick-Hall on gramps#2299.) Core changes target a different branch — see the Core Development — Rules page. - MUST: branch from
upstream/<base>, not the fork's tracking copy — fork bases drift (e.g. PRs 2315/2316 carried a strayAGENTS.mdfrom the fork). - MUST NOT: bump the addon's
versionfield in an addons-source PR. The maintainer manages versions centrally. (Caught on PR 911, bug 12572.) - MUST: a bug-fix PR includes a regression test, or an explicit "no test because X" rationale plus a manual repro. "Add the test later" is not an option.
- MUST: structure the PR body as Root cause / Fix / Verified against / Test, citing
path:lineson the branch the PR targets. - MUST: when the PR modifies an addon, call out its current maintainer — add an
## Affected addonsection to the PR body that @-mentions the addon's current maintainer, a heads-up so they are aware of the change and don't miss it. This is awareness, not attribution. "Current maintainer" = the addon's.gpr.pymaintainersfield when declared, otherwise itsauthors(an addon with no separate maintainer is maintained by its original author — Doug's "original developer (or contributors)" and Nick's "current maintainer" are the same role). The.gpr.pyrecords names/emails, not GitHub handles — resolve a handle best-effort from the declared email so the mention notifies, and name the person when no handle resolves. (Raised on addons-source PR #946 — Doug Blank: "otherwise I could miss fixes to my addons"; Nick Hall: "mention the current maintainer if one exists.") - OPTIONAL: reference the Mantis bug in the PR body when one exists — a Mantis reference is optional for addons-source, since many addon fixes have no Mantis ticket (they're tracked as fork GitHub issues, or are ticketless). addons-source also does not use the
Fixes #NNNNcommit-message trailer at all — that is the core convention; see the Core Development — Rules page. - MUST: keep upstream-repo cross-references out of PR text and fork issues — reference other upstream PRs/issues in plain text ("upstream PR 949"), never a GitHub URL or
owner/repo#NNNcross-ref (it back-links/notifies that thread). The#nnnnMantis reference and the PR's own target are exempt. Authoritative:docs/INTEGRATION.md§"No upstream-repo links". - MUST NOT: merge across branches. Rebase rather than merge — PRs with merge commits are rejected upstream.
- MUST NOT: cosmetically update in-flight upstream PRs. Parity, "rebase is clean," and "branch is behind" are not reasons to force-push. Push only when a specific correctness issue needs fixing.
- SHOULD: before writing any fix, check upstream isn't ahead — merged history on the target branch AND
master, plus closed and rejected PRs on the affected file (not just the bug number). Closed PRs are signal: a closed-unmerged PR with the same fix shape is the maintainer's "no." - SHOULD: if a PR already exists for the bug, verify it instead of duplicating. Merged → confirm-and-close; open → review and defer to the maintainer; closed → treat as the maintainer's "no."
- SHOULD: reproduce against
example.grampsfirst — it's the canonical fixture and "couldn't reproduce" is the most common reason a fix stalls in triage. - MAY: open as a draft PR for early review or to publish work-in-progress; mark ready when the change is complete and the author has re-read the diff with fresh eyes.
Verification before commit
- MUST: find a test procedure before committing — local run, dry-run, snippet check. Never commit untested changes.
- MUST: treat a green mechanical check (lint,
git cherry-pickapplies, build green,py_compileexits 0) as evidence of that narrow check, not of correctness. Name what the check verified and what it left unverified. - MUST: after pushing a PR branch, watch the PR's CI checks until they finish (e.g.
gh pr checks <PR#> --watch). Local pre-commit catches static checks only; test failures surface in CI's actual unit-test run.
Commit messages
Commit messages are parsed by scripts that update Mantis BT and generate the ChangeLog / News files for releases. Formatting must be followed precisely.
- MUST: the first line is a short summary, ≤ 70 characters.
- MUST: the description is separated from the summary by a single blank line, and wrapped at 80 characters.
- MUST: describe the change from the user's perspective. Don't recap the diff —
git diffexists. - SHOULD: use complete sentences in the description.
- MUST: reference another commit by its full hash, not a short hash. GitHub auto-hyperlinks full hashes; short hashes in brackets do not link.
- MUST: the Mantis trailer is on the last line of the commit message, separated from the description by a single blank line.
Mantis trailer keywords
To resolve a bug (closes it on commit):
Fixes #12345 Fixed #12345 Resolves #12345 Resolved #12345 Fixes #12345, #67890
To link to a bug (cross-reference without closing):
Bug #12345 Issue #12345 Report #12345 Bugs #12345, #67890
Bare numbers (no #) and URLs both miss the auto-link — use the #NNNN form. Note this is the opposite of the convention inside MantisBT itself, where #NNNN auto-links to another Mantis issue and bare numbers are preferred; here, inside Git commit messages and GitHub PR bodies, #NNNN is what hooks the MantisBT scripts.
For the trailer to wire up on Mantis, the Git author or committer has to be a developer on the Mantis bug tracker. The Git name must match the Mantis username or real name, or the Git email must match the Mantis email.
addons-source: bug reference in PR body
addons-source PRs don't use Fixes #NNNN in the commit message — that trailer is the core convention. A Mantis bug reference in the PR body is optional: include it when the fix has a Mantis ticket, but many addon fixes have none (fork GitHub issue, or ticketless), and those need no reference. A present-but-malformed reference is still wrong.
See also
- Overview
- Fundamentals
- Testing
- Code analysis
- Packaging
../gramps/AGENTS.md— the full Python coding standard inherited here.- addons-source CONTRIBUTING.md
- Committing policies — upstream's commit-message + Mantis-trailer rules.
|
This article's content is incomplete or a placeholder stub. |