User:Eduralph/Sandbox/Gramps 6.0 Wiki Manual - Addon Development - Code Analysis
← Previous · Index · Next →
Contents
Overview
What automated checks run against addon code, locally and in CI, and how to keep an addon passing them. The goal is "PR opens green" — every check below catches a class of issue cheaper than a maintainer review round.
The checks vary by repo. Two combinations matter; the cheat sheet:
| Check | gramps core | addons-source |
|---|---|---|
Black formatting (--check --diff)
|
pre-commit + CI | — |
mypy static types
|
pre-commit + CI | — |
ruff E9 / F63 / F7 / F82
|
— | pre-commit + CI |
python -m py_compile / ast.parse
|
— | pre-commit + CI |
msgfmt on po/*.po
|
upstream build | upstream build |
pylint ≥ 9 on new files
|
manual; not gated | not enforced |
addons-source does not enforce Black today; gramps core does. This is the most common surprise for authors moving between the two. See Black below.
Pre-commit
There is no published upstream .pre-commit-config.yaml for addons-source; addon authors can install their own locally to mirror the CI gates above (ruff, py_compile), but this is convenience tooling, not an upstream requirement. The authoritative checks live in addons-source/.github/workflows/ci.yml; if your local pre-commit and CI disagree, CI wins.
For gramps core, the upstream pre-commit config under the gramps/ repo covers Black and mypy; install with the standard pre-commit install flow from the repo root.
Black
Black is an opinionated Python formatter. Where enforced, the CI lint job is psf/black@stable --check --diff; a violation fails the build and blocks merging.
Where enforced. gramps core (maintenance/gramps61 and master) — both pre-commit and CI. addons-source does not enforce Black today; PRs there go through without formatting checks.
The trap on gramps core. Tiny diffs that look harmless trip the gate:
- A mid-module-import blank line.
- A multi-line
.append()collapsed to one line.
PR 2326 tripped this on cli/clidbman.py and a new test file; the fix was a Black-cleaned force-push rebase. Run black --check on the changed files before pushing:
git diff --name-only --diff-filter=ACMR origin/master...HEAD \ | grep '\.py$' \ | xargs --no-run-if-empty black --check --diff
mypy
Gramps core's CI runs mypy against the tree; type errors block the build. *.gpr.py plugin registration files are excluded (they run in the injected-name scope and would otherwise complain about register, _, etc.).
This applies to gramps core only. addons-source PRs don't run mypy; addon Python doesn't ship type hints by default. Where an addon does add type hints, prefer the 3.10+ shape (X | None, list[X]) per [16-guidelines → Coding styleN-guidelines.md#coding-style).
ruff E9 / F63 / F7 / F82
addons-source's pre-commit and CI run ruff with a tight rule selection:
| Code | Catches |
|---|---|
E9*
|
Syntax errors |
F63
|
Comparison and membership operator mistakes (is not vs not is)
|
F7
|
Imports inside dead code, syntax-level structural issues |
F82
|
Undefined names |
It's a syntax-and-undefined-names net, not a style enforcer — the goal is "code that imports", which is what gets you past the plugin-discovery gate.
Local invocation:
ruff check --select E9,F63,F7,F82 <Addon>/
The undefined-name rule (F82) is the most useful single check for addon authors — Pillow typo'd as Pilllow, gramps.gen.plugin typo'd as plugn, the kind of typo that produces a silent skip in the plugin manager and no traceback. ruff F82 catches them.
A lint flag is a symptom, not the bug. Don't just add a # noqa or a defensive import to silence F82 — read the enclosing function first. An undefined name in shipping code usually means dead or broken code.
python -m py_compile / ast.parse
Both pre-commit and addons-source CI compile every changed .py:
python -m py_compile <Addon>/*.py
ast.parse is the more lenient check (won't import code, just parses); both exist because a file that compiles can still fail to import (NameError at module-level, missing dep). The compile pass is the absolute floor — failing it means the addon can't even register.
msgfmt on po/*.po
Per-addon .po files have to compile to .mo cleanly for translations to take effect at runtime. A malformed catalog — mismatched %s substitutions, unclosed plural-form expression — silently falls back to English on the platform where compilation fails. Run msgfmt -c on every per-addon catalog before publishing.
Local check:
make.py gramps60 compile <Addon>
make.py compile wraps msgfmt with the right paths; a failure prints the offending file and line. See [12-packaging → The localisation flowK-packaging.md#the-localisation-flow).
Mantis 14234 (lxml ngettext newline fix; addons-source PR 907) is the canonical example — a single misplaced newline in a plural form, caught by a msgfmt -c pass.
pylint
Gramps' programming guidelines call for pylint ≥ 9 on new files and "changes to existing files shall not reduce the pylint score" — but this is not gated by CI. It's developer guidance, not a hard check.
pylint doesn't run on addon code by default. When you do run it locally:
pylint --disable=missing-docstring <Addon>/<Addon>.py
Run from the addon's parent directory so pylint's import resolution finds it as a package.
Verifying requires_mod
requires_mod takes the importable module name, not the PyPI distribution name (see 10-troubleshoot → `requires_mod` declares `Pillow`…). Before pushing, on a system with the dependency installed:
from importlib.util import find_spec
for mod in ["PIL", "lxml", "dateutil"]:
assert find_spec(mod) is not None, mod
This is a manual check, not a CI gate. It's listed here because the failure mode it catches — silent skip in the plugin manager — looks exactly like a ruff F82 symptom but happens at a different layer.
Coding-standard rules worth running locally
The full standard lives in ../gramps/AGENTS.md and applies to all Gramps-related Python. The mechanical checks above cover formatting and syntax; the rules below need a manual pass.
Import grouping
Three sections, each with a comment header:
# ------------------------------------------------------------------------- # # Standard Python modules # # ------------------------------------------------------------------------- import os import logging # ------------------------------------------------------------------------- # # GTK/Gnome modules # # ------------------------------------------------------------------------- from gi.repository import Gtk # ------------------------------------------------------------------------- # # Gramps modules # # ------------------------------------------------------------------------- from gramps.gen.db.base import DbReadBase from .mymodule import MyClass
Existing code that doesn't follow this stays as-is; new code does.
Callback names
Callbacks are prefixed cb_:
def cb_save(self, *args):
...
pylint also avoids the W0613: Unused argument warning for cb_*-prefixed methods, which is convenient for GTK signal handlers that receive arguments they don't use.
Class headers
Every class — including unittest.TestCase subclasses — carries a navigation comment header:
# ------------------------------------------------------------
#
# MyClass
#
# ------------------------------------------------------------
class MyClass:
...
This is for finding the class when multiple classes share a file, not for documentation. Sphinx-style docstrings handle the documentation.
Member-name conventions
__private(two underscores) — class-only access._protected(one underscore) — class and subclass access.
PEP 8 with one local addition: a space after every comma.
TAB stops
No TABs in Python. Indentation is 4 spaces. Where TABs are unavoidable (Makefiles), they're at columns 9, 17, 25, … (equivalent to 8 spaces). Don't set your editor's TAB stops to 4 — that "fixes" indentation by making TABs invisible and produces files that look right but parse wrong.
Running everything locally before pushing
A pragmatic checklist before opening a PR:
# Addons-source PRs: ruff check --select E9,F63,F7,F82 <Addon>/ python -m py_compile <Addon>/*.py make.py gramps60 compile <Addon> # exercises msgfmt python -m unittest discover -s <Addon>/tests -t . # tests # Gramps core PRs add: black --check --diff <changed-files>.py mypy GRAMPS_RESOURCES=. python3 -m unittest discover -p "*_test.py"
See 12-packagingK-packaging.md) for `make.py` setup and [08-testing for the test-loading conventions.
See also
- 05-fundamentals — the conventions the static checks verify.
- 08-testing — the runtime checks that complement static analysis.
- 10-troubleshoot → "PR's pre-commit passed but CI is red" — the most common code-analysis-related symptom.
- 12-packagingK-packaging.md) — `make.py` invocations. - [16-guidelines → Coding styleN-guidelines.md#coding-style), [16-guidelines → Verification before commitN-guidelines.md#verification-before-commit) — normative rules. - [Programming guidelines — the standalone wiki page; primary scraped source.
../gramps/AGENTS.md— the full Python coding standard, inherited from gramps core.
|
This article's content is incomplete or a placeholder stub. |