User:Eduralph/Sandbox/Gramps 6.0 Wiki Manual - Addon Development - Data access
← Previous · Index · Next →
Contents
Overview
Every addon that does anything useful with a family tree reads or writes through the database API — the DbReadBase / DbWriteBase interface implemented by Gramps' database backends (BSDDB historically, SQLite from 6.0 onward).
You don't instantiate a database yourself. The plugin loader hands you a DbState object; the live database is dbstate.db. Everything below is methods on that handle.
db = dbstate.db # this is your entry point
The same db works for read-only addons (reports, gramplets, quick views) and for tools that mutate data. Mutation goes through transactions; see Mutating data below.
Identifying objects: handles vs Gramps IDs
Every primary object (Person, Family, Event, Place, Source, Citation, Repository, Media, Note, Tag) has two identifiers:
| Identifier | Stable | Format | Used for |
|---|---|---|---|
| Handle | Yes (internal, never reused) | 32-char hex string | Cross-references in the database |
| Gramps ID | User-renameable | I0001, F0001, E0001, ...
|
User-visible labels and external interop |
Rule of thumb: use handles inside your code; show Gramps IDs to the user. Handles never change; Gramps IDs do (the user can edit them, the "Reorder Gramps IDs" tool can rewrite them in bulk).
# Right: traverse by handle
person = db.get_person_from_handle(handle)
# Right: show the user a Gramps ID
print(f"Working on {person.gramps_id}")
# Wrong: traverse by Gramps ID (works, but slower and breaks under reorder)
person = db.get_person_from_gramps_id("I0001")
Each object class has both lookup methods (get_<type>_from_handle and get_<type>_from_gramps_id); see 07-api-reference for the full list.
Reading: one object at a time
The fastest pattern, when you have a handle in hand:
person = db.get_person_from_handle(person_handle) family = db.get_family_from_handle(family_handle) event = db.get_event_from_handle(event_handle)
Each returns None if the handle isn't in the database (deleted, broken reference). Always guard:
person = db.get_person_from_handle(handle)
if person is None:
return # silently skip, or raise a HandleError if the caller expects one
For HandleError and friends, import from gramps.gen.errors.
Reading: iterating all objects
For reports and surveys you'll want every object of a given type. The database exposes one generator per object class:
for person in db.iter_people():
...
for family in db.iter_families():
...
These are generators, not lists — they stream through the database without loading everything into memory. Don't call list(db.iter_people()) on a 50,000-person tree unless you have a reason.
To iterate just the handles (cheaper when you only need to count or filter):
for handle in db.iter_person_handles():
...
Counts come without iteration:
db.get_number_of_people() db.get_number_of_families() db.get_number_of_events()
Following references
Most addons don't visit objects in isolation — they follow the relationships between them. Gramps' object model exposes references as handle lists on the parent object.
Person → families they're a parent in:
for family_handle in person.get_family_handle_list():
family = db.get_family_from_handle(family_handle)
...
Person → events:
for ref in person.get_event_ref_list():
event = db.get_event_from_handle(ref.ref)
role = ref.get_role()
...
event_ref carries more than the handle — also the role (Primary, Witness, etc.) and any private flag. Read the ref, then dereference if you need the event itself.
Family → children:
for child_ref in family.get_child_ref_list():
child = db.get_person_from_handle(child_ref.ref)
...
The full handle-list / ref-list inventory per object class lives in the Gramps API docs; see also 07-api-reference for the addon-facing subset.
Backlinks: who refers to this object?
The forward direction (person → events they participated in) lives on the object. The reverse direction (event → people who participated in it) lives on the database:
for (obj_type, obj_handle) in db.find_backlink_handles(event.handle):
if obj_type == "Person":
person = db.get_person_from_handle(obj_handle)
...
find_backlink_handles returns (class_name, handle) tuples for every primary object that references the given handle. Use it for:
- Finding all sources that cite a given place
- Finding all people present at a given event
- Detecting orphaned objects (no backlinks → unreferenced)
Note that obj_type is the class name as a string ("Person", "Family", ...), not the Python class itself.
Filters
For non-trivial selection (e.g. "all people born in Hamburg between 1850 and 1900"), use Gramps' filter framework rather than hand-rolling predicates:
from gramps.gen.filters import GenericFilterFactory
GenericFilter = GenericFilterFactory("Person")
filt = GenericFilter()
filt.add_rule(SomeRule([arg1, arg2]))
handles = filt.apply(db, db.iter_person_handles())
Filters compose, cache, and integrate with the GUI's filter sidebar — a report that defines its own filter gets it as a sidebar option for free. The rule catalogue lives under gramps.gen.filters.rules; the user-facing counterpart is documented in Filters.
Mutating data
Write addons (mainly tools) modify data through transactions. The pattern is always the same:
with DbTxn(_("Tool name: what it did"), db) as trans:
person = db.get_person_from_handle(handle)
person.set_privacy(True)
db.commit_person(person, trans)
Three things matter:
- The transaction message is user-visible in the Undo History. Make it descriptive and translated.
- Always
db.commit_<type>(obj, trans)after mutating — the object is a copy; commit writes it back. - Group related changes in one transaction so the user can undo as a single step.
Creating a new object follows the same shape:
from gramps.gen.lib import Person, Name
with DbTxn(_("Add unknown spouse"), db) as trans:
person = Person()
name = Name()
name.set_surname(surname)
person.set_primary_name(name)
person.gramps_id = db.find_next_person_gramps_id()
db.add_person(person, trans)
find_next_<type>_gramps_id() allocates an unused ID; add_<type>() inserts and assigns the handle.
Testing data access
Two complementary approaches:
- Real-data tests — load
example.gramps(shipped with Gramps, canonical test fixture) and exercise your code against it. Best for catching real-world data quirks (cross-typed backlinks, ID normalisation, unusual character sets). See 08-testing. - Mocked tests — substitute the database with a stub that returns fixed objects. Best for tight unit-test loops that don't need a database on disk.
The lesson, learned the hard way: mocked DB tests can pass while the real-DB code is broken, because the mock doesn't reproduce the cross-typed backlinks and ID quirks of a populated tree. Prefer example.gramps for anything that traverses the DB; reserve mocks for pure helpers.
Performance notes
The database API is fast enough that most addons don't need to think about performance. When you do:
- Iterating handles is cheaper than iterating objects — only dereference when you need the object's contents.
get_number_of_<type>()is O(1);len(list(db.iter_<type>()))is O(n).- Backlinks aren't free — they read an index but still scan it. Don't call
find_backlink_handlesin a tight inner loop. - The 5.x → 6.0 SQLite backend is roughly comparable to BSDDB for reads, faster for writes. Avoid backend-specific assumptions; addons should work on either.
See also
- 05-fundamentals — the plugin lifecycle that wraps this DB access in
- 07-api-reference — the addon-facing API surface
- 08-testing — testing strategies, real-data vs mocks
- Using database API — the standalone wiki reference, covers backends and internals in more depth
- Gramps Developer Reference — the full API docs