User:Eduralph/Sandbox/Gramps 6.0 Wiki Manual - Addon Development - Data access

From Gramps

← Previous · Index · Next →


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

Fig. 1 — Gramps primary objects and the most-traversed relationships. Edges labelled <Type>Ref (e.g. EventRef, CitationRef, MediaRef) go through a ref object that carries metadata such as the role or relationship; bare-labelled edges are direct handle references. Notes and Tags can be attached to any primary object and are omitted to keep arrows readable. Reverse traversals — "who refers to this object?" — go through db.find_backlink_handles() instead of these forward links; see Backlinks below.

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:

  1. The transaction message is user-visible in the Undo History. Make it descriptive and translated.
  2. Always db.commit_<type>(obj, trans) after mutating — the object is a copy; commit writes it back.
  3. 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_handles in 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