This document specifies rules and conventions that agents should follow when making changes to the Gramps codebase.
Run from source (no install):
python3 Gramps.pyBuild wheel:
python3 -m build --wheelRun all tests:
export GDK_BACKEND=-
export GRAMPS_RESOURCES=build/share
export LANGUAGE=
export LANG=en_US.utf-8
python3 -m unittest discover -p "*_test.py"Run a single test module or method:
python3 -m unittest gramps.gen.db.test.db_test
python3 -m unittest gramps.gen.db.test.db_test.TestGenericDb.test_get_personType checking:
mypyCode formatting (must pass CI):
black .
# Check only:
black --check .Gramps is a GTK3 genealogy application. The codebase is split into four main packages:
lib/— Data model:Person,Family,Event,Place,Source,Citation,Repository,Media,Note,Tag. All inherit fromPrimaryObject(has a database handle) orSecondaryObject.db/— Database abstraction layer.DbReadBase/DbBaseare the abstract interfaces.generic.pyis the DBAPI-based implementation used by SQLite, DuckDB, MySQL, PostgreSQL backends. Transactions viaDbTxn; undo/redo built-in.plug/— Plugin system core:BasePluginManager(singleton),PluginRegister, plugin metadata, and basePluginclass.filters/,datehandler/,display/— Shared utilities with no GUI imports.
editors/— Modal dialogs for editing each primary object type.views/— Navigation views (People, Families, Events, etc.) that populate the main window.grampsgui.py— Main window and view-switching logic.dbman.py— GUI database manager (open/close/import/export).configure.py— Preferences dialog.glade/— GTK UI definition files (.glade/.ui).
db/— Database backends (bsddb legacy, dbapi/SQLite default, duckdb, mysql, postgresql).gramplet/— ~50 dashboard widgets (mini read-only views). Each is a self-contained gramplet.importer/,export/— GEDCOM, XML, CSV, and other formats.view/,tool/,textreport/,drawreport/,quickview/,sidebar/— Other plugin categories.
grampscli.py— Non-GUI entry point (import/export/script automation).arghandler.py— Argument parsing and dispatch.
Plugins are discovered from ~/.local/share/gramps/grampsx.x/plugins/ (user) and the system prefix. Each plugin registers itself via a gpr.yaml file or a register() call. The BasePluginManager singleton loads and manages them. Categories: Importers, Exporters, DB backends, Gramplets, Views, Tools, Reports, Quick views, Sidebars, Map services.
The default backend is DBAPI (SQLite via gramps/plugins/db/dbapi/). All backends implement the DbReadBase/DbBase interface from gramps/gen/db/. Schema migrations live in gramps/gen/db/upgrade.py.
The CI pipeline (.github/workflows/gramps-ci.yml) runs: build wheel → mypy → unittest discover. The black.yml workflow checks formatting on every push. Both must pass before merging.
All changed Python files must be formatted with Black before committing. CI enforces this on every PR. Run:
git diff --name-only --diff-filter=ACMR origin/master...HEAD | grep '\.py$' | xargs --no-run-if-empty blackAll functions and methods must have type hints using Python 3.10+ syntax:
- Use
X | Noneinstead ofOptional[X] - Use
X | Yinstead ofUnion[X, Y] - Use
list[X],dict[K, V],tuple[X, ...]instead ofList,Dict,Tuplefromtyping
- Use handle types from
gramps/gen/types.py(e.g.,PersonHandle,FamilyHandle) rather than barestrfor handles - Use Gramps ID types from
gramps/gen/types.py(e.g.,PersonGrampsID,FamilyGrampsID) rather than barestrfor gramps_id
All functions and methods must have docstrings. Keep them concise — a short description is almost always enough:
def close(self):
"""
Close the specified database.
"""Use the full Sphinx format (:param:, :type:, :returns:, :rtype:) only when the parameters or return value are non-obvious and not already expressed by type hints:
def get_person_from_gramps_id(self, gramps_id: PersonGrampsID) -> Person | None:
"""
Return a Person from the database using the given gramps ID.
:param gramps_id: The gramps ID of the Person to retrieve.
:type gramps_id: PersonGrampsID
:returns: The Person object, or ``None`` if not found.
:rtype: :py:class:`Person`
"""Do not add verbose multi-line docstrings to simple functions, and never repeat information already expressed by the function name or type hints.
Imports must be organized into sections, each preceded by a comment header of this form:
# -------------------------------------------------------------------------
#
# 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 MyClassNot all sections are required — include only those that apply. Common section names used in the codebase include Standard Python modules, GTK/Gnome modules, and Gramps modules.
Code should be PEP 8 compatible, except where that conflicts with Black formatting. Black takes precedence. Use 4 spaces for indentation — never TABs.
Run pylint on new code. Ideally new code should score 9 or higher and changes should not reduce the overall pylint score. This is not strictly enforced and should never come at the expense of code clarity, Black formatting, or any other rule in this guide.
Inline suppression comments such as # pylint: disable=import-outside-toplevel are not acceptable.
Each class must have a simple header comment to help locate it when multiple classes exist in the same file. This is for navigation, not documentation:
#------------------------------------------------------------
#
# MyClass
#
#------------------------------------------------------------
class MyClass:
...Callback function names must be prefixed with cb_. For example: cb_my_callback.
Use module-level loggers; do not use print() for diagnostic output:
import logging
LOG = logging.getLogger(__name__)All user-visible strings must be wrapped with _() for translation support:
raise ValueError(_("Invalid handle: %s") % handle)The alias _(string , context) is preferred to pgettext(context, message).
Use ngettext(singular, plural, n) for plural forms.
Files in the gen submodule must not import from any other Gramps submodule (e.g., gui, plugins). The gen submodule must remain self-contained.
Every new .py file must include a GPL-2.0-or-later license header with copyright:
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) <YEAR> <Author Name>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#- Write tests for each distinct flow of logic (happy path, error cases, edge cases).
- Tests must use the
unittestframework — do not usepytest. - Test files must be named with the
_test.pysuffix (e.g.,mymodule_test.py). CI discovers tests using*_test.py. - Place test files in a
test/subdirectory alongside the module being tested.
export GDK_BACKEND=-
export GRAMPS_RESOURCES=build/share
python3 -m unittest discover -p "*_test.py"Changes must pass mypy static analysis:
mypyNote: *.gpr.py plugin registration files are excluded from mypy checks.
Commit messages are parsed by scripts that update Mantis BT and generate ChangeLog and News files, so formatting must be followed precisely.
- The first line is a short summary of the change — maximum 70 characters.
- All other lines must be wrapped at 80 characters.
- Describe how the change affects functionality from the user's perspective.
- Use complete sentences when possible.
- To reference another commit, use the full commit hash (not a short hash).
- The last line must link to or resolve a bug report using the Mantis BT integration keywords. Example:
Fix crash when opening event editor with empty date field.
When a date field was left blank, the event editor raised an unhandled
AttributeError. This change adds a guard so the field is treated as an
empty string instead.
Fixes #12345.
To mark a bug as fixed use the Mantis BT special keywords Fixes, Fixed, Resolves or Resolved followed by a # and then the bug number without leading zeros. e.g. Fixes #12345..
To reference an issue the keywords Bug, Bugs, Issue, Issues, Report or Reports may be used with a bug number in the same format for bugs. e.g. Issue #12345.
Multiple issues may be referenced. e.g. Resolves #12345, #12346.
These references should be used in the last line of a commit message.
When adding or removing Python source files, update the translation file lists accordingly:
po/POTFILES.in— list of files that contain translatable strings.po/POTFILES.skip— list of files that intentionally have no translatable strings and should be excluded from translation checks.
- Prefer existing exceptions from
gramps/gen/errors.pyandgramps/gen/db/exceptions.pyover creating new ones. - Only introduce a new exception class when none of the existing ones accurately represent the error condition.
- Raise
HandleErrorfor invalid or missing handles.
Branch merges are not allowed in pull requests. Rebase rather than merging.
All pull requests must be submitted from a branch in your personal fork of the repository, not from a branch pushed directly to the upstream gramps-project/gramps remote. Even if you have write access to the upstream remote, create your branch and submit the PR from your fork.