User:Eduralph/Sandbox/Gramps 6.0 Wiki Manual - Addon Development - Troubleshoot
← Previous · Index · Next →
Contents
- 1 Overview
- 2 Loading and discovery
- 3 Imports and Python namespace traps
- 3.1 "from <Addon> import <Addon> binds the submodule, not the class."
- 3.2 "requires_mod declares Pillow but Gramps says it's missing."
- 3.3 "requires_gi declaration is fine on 6.0, broken on 6.1."
- 3.4 "Backlinks return (class_name, handle) not (class, handle)."
- 3.5 "The fix worked in my mocked test but breaks on example.gramps."
- 4 Translation and locale
- 5 Testing
Overview
The failure modes that bite first-time addon authors, organised by symptom. Each entry is "what you see → why → what to do." Read this sideways: jump to the symptom that matches what you're seeing, follow the link out to the relevant chapter for the fix in depth.
For technique-level coverage (pdb, gdb, profilers), see 09-debug. For the normative rules an addon must satisfy, see [15-guidelinesN-guidelines.md).
Loading and discovery
The addon failed to register. Three usual causes, in order of likelihood:
.gpr.pyraised at import. Plugin discovery executes every.gpr.pyat startup; aSyntaxErroror import failure there silently drops the addon from the catalog. Launch from a terminal to see the traceback on stderr, or check the Gramps log window (Help → Log) for the failure entry.gramps_target_versionmismatch. A6.0addon won't load in 6.1, and vice versa. Plugin discovery silently skips the registration entry. See [13-compatibility →gramps_target_versionsemanticsL-compatibility.md#gramps_target_version-semantics).iddoesn't match the folder name. The addon's folder name and theidargument toregister(...)must be identical. Gramps does not match by content — it matches by folder name and verifies againstid. A mismatch silently drops the entry.
The fastest check: in a Python REPL with gramps on sys.path, exec(open("MyAddon/MyAddon.gpr.py").read()). If it raises, you have your cause; if it returns silently and there's no entry, your register() call is being filtered out.
"My edits to the plugin file disappeared on restart."
The user plugin directory (~/.local/share/gramps/gramps60/plugins/…) is the auto-sync target. Edits there are silently overwritten on the next save from addons-source/.
The fix. Edit in addons-source/<Addon>/ and let the sync flow do its job — see [12-packaging → Editing addons-source/, not the live plugin directoryK-packaging.md#editing-addons-source-not-the-live-plugin-directory).
On Gramps 6.1+ Linux/macOS, symlinking the working tree into the user plugin directory once eliminates the copy step (commit 9443dcbb30); on Gramps 6.0 and on Windows generally, the copy / rsync loop remains.
"The addon's folder is there but Gramps doesn't load it."
The most common variants of the previous symptom, when ruled out:
- 6.0 only: the folder is reached via a symlink. Gramps 6.0 plugin discovery does not follow symlinks; use a physical copy or upgrade to 6.1+. (See [13-compatibility → Plugin discovery follows symlinksL-compatibility.md#plugin-discovery-follows-symlinks).)
- Windows, any version: same as above — the 6.1 symlink test is skipped on Windows because the platform's symlink behaviour is inconsistent without elevated privileges. Physical copy.
.gpr.pynot at top level of folder: the registration file has to be<Addon>/<Addon>.gpr.py, not in a subfolder.
Imports and Python namespace traps
"from <Addon> import <Addon> binds the submodule, not the class."
The classic Gramps namespace-package trap.
The addon folder is a namespace package (PEP 420), so importing <Addon> gives you the package, not the class inside the like-named module. Code that worked under discover-based test loading breaks under dotted-path loading because the resolution path is different.
The fix. Use the explicit submodule form:
# Wrong: binds the package (silently — until you try to use the class) from MyAddon import MyAddon # Right: binds the class inside the module from MyAddon.MyAddon import MyAddon
Mantis bug 12691 is the canonical case. Upstream CI loads addon tests by dotted path rather than discover exactly to surface this trap; see 08-testing.
"requires_mod declares Pillow but Gramps says it's missing."
requires_mod takes the importable module name, not the PyPI distribution name:
| PyPI name | Importable name |
|---|---|
Pillow
|
PIL
|
PyYAML
|
yaml
|
lxml
|
lxml
|
python-dateutil
|
dateutil
|
Beautifulsoup4
|
bs4
|
The check. Before pushing, verify on a system with the package installed:
from importlib.util import find_spec
assert find_spec("PIL") is not None
If find_spec returns None, the name in requires_mod is wrong.
"requires_gi declaration is fine on 6.0, broken on 6.1."
GExiv2's version handling was rewritten on maintenance/gramps61 only (addons-source PR 829). An addon with requires_gi=[("GExiv2", "0.10")] that works on 6.0 may need a different pin on 6.1.
The fix. Read the EditExifMetadata addon's GExiv2 code on the target branch before assuming a pin transfers. See 13-compatibility → GExiv2 version handling rewrittenL-compatibility.md#gexiv2-version-handling-rewritten).
## Database access
### "My addon iterates the DB but raises `KeyError` halfway through."
The DB contains a reference to a handle that no longer resolves. This happens in real-world data; mocked tests don't exhibit it because mocks always return the same fixed set.
**The fix.** Always guard handle dereferences:
```python
event = db.get_event_from_handle(handle)
if event is None:
continue # silently skip dangling reference
```
See [06-data-access → Reading: one object at a time for the pattern. The same shape applies to every get_<type>_from_handle call.
"Backlinks return (class_name, handle) not (class, handle)."
db.find_backlink_handles(handle) yields tuples whose first element is the class name as a string ("Person", "Family", …), not the Python class itself. The most common bug here is isinstance checks that never match.
# Wrong:
for cls, h in db.find_backlink_handles(handle):
if cls is Person: # always False — cls is "Person"
...
# Right:
for type_name, h in db.find_backlink_handles(handle):
if type_name == "Person":
...
"The fix worked in my mocked test but breaks on example.gramps."
Real data has shapes the mock doesn't model:
- Cross-typed backlinks — a Source can be backlinked from a Person, a Family, an Event, a Place, a Media, a Note, a Citation, and a Repository. Mocks tend to model only the type the test author was focused on.
- ID normalisation —
I0001vsI0021vsI12345. A regex that matches the mock's 4-digit IDs misses the real data's variable-width IDs. - Optional fields actually being absent —
person.get_birth_ref()returnsNonein real data far more often than in mocks.
The fix. Add an example.gramps-backed test alongside the mock. See 06-data-access → Testing data access and 08-testing.
Translation and locale
"My addon translates fine on Linux, not on Windows" (or vice versa).
The two platforms set up the locale differently:
- Linux — needs
locale-genfor the language and theLANGUAGEenv var set (not justLANG). - Windows — reads
LANGdirectly viawin32locale.py, no OS locale config needed.
The fix in repro scripts. Sidestep both by instantiating GrampsLocale(localedir, languages) directly — see 09-debug → Reproduction scripts that bypass the GUI.
The fix in production. Make sure the per-addon .po files compile cleanly on both platforms (make.py compile <Addon> in addons-source). A .mo file that's missing or malformed will silently fall back to English on whichever platform fails to load it.
"Strings I marked with _() aren't translated."
You've forgotten to bind _ to the addon's catalog. At the top of the implementation module:
from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.get_addon_translator(__file__).gettext
Without that line, _() falls back to Gramps' core catalog rather than the addon's own <Addon>/po/ translations. The strings stay English regardless of UI language. See 05-fundamentals → Translation.
Testing
"Tests pass locally, fail in CI."
Three common causes:
- Filename prefix wrong for the platform.
test_linux_*.pyis skipped on the Windows runner,test_windows_*.pyis skipped on Linux. A test you intended as cross-platform but accidentally named with a prefix runs only where the prefix points. See 08-testing → Filename conventions. requires_moddeps assumed in tests. Addon tests must be runnable without the addon'srequires_moddependencies installed in the test Python — Mac contributors can't easily install addon deps into the Gramps Python (Gary Griffin, 2026-05-16). Mock at the import boundary or skip cleanly.- Test loaded by dotted path surfaces the namespace trap. Local
discoverfromtests/would hide thefrom <Addon> import <Addon>bug; CI loads by dotted path (<Addon>.tests.<module>), which exposes it. See bug 12691.
"PR's pre-commit passed but CI is red."
Pre-commit catches static checks only. Test failures (e.g. an import that breaks at module load) surface in CI's actual unit-test run, not in pre-commit. After pushing, watch the PR's checks until they finish:
gh pr checks <PR#> --watch
- 08-testing — the test conventions that catch many of these symptoms before they reach a user.
- [12-packagingK-packaging.md) — the source-to-distribution flow, where the "edits disappeared" trap lives.
- [13-compatibilityL-compatibility.md) — the 6.0 vs 6.1 deltas behind several entries here.
- [15-guidelinesN-guidelines.md) — normative reference; this chapter describes what goes wrong, that chapter describes what must hold.
- Mantis bug tracker — where the recurring failures get filed.
|
This article's content is incomplete or a placeholder stub. |