Getting Started¶
This guide walks through the most common workflows: connecting to the API, managing family trees, adding persons, recording relationships, and handling errors.
Prerequisites¶
Install pyclann:
You need a running Clann server and credentials for the ullav-user-management auth service.
Connecting and authenticating¶
from pyclann import ClannClient
client = ClannClient(
api_url="http://localhost:8090",
auth_url="http://localhost:8081",
)
info = client.login(email="you@example.com", password="secret")
print(info.username, info.roles)
If your Clann server and auth service share a hostname (e.g. behind a reverse proxy), you can omit auth_url:
client = ClannClient("https://clann.example.com")
client.login(email="you@example.com", password="secret")
Creating a family tree¶
trees.create() creates a new tree with a globally unique slug:
tree = client.trees.create(
"walsh-family", # slug — used in API paths and as the tree identifier
"Walsh Family", # human-readable display name
owner="alice",
is_primary=True, # mark as the owner's primary tree
)
print(f"Created tree: {tree.display_name} (id={tree.id})")
List trees and filter by owner or team:
Upload a tree avatar:
with open("family-crest.jpg", "rb") as f:
client.trees.upload_image("walsh-family", f.read(), "image/jpeg")
# Download the image (public endpoint — no auth required)
image = client.trees.get_image("walsh-family")
Adding persons¶
persons.create() adds a person to one or more trees:
from pyclann import Sex
father = client.persons.create(
family_name="Walsh",
first_name="Patrick",
sex=Sex.MALE, # or the plain string "Male"
trees=["walsh-family"],
date_of_birth="1820-06-01",
place_of_birth="Galway",
created_by="alice",
)
print(f"Added: {father.first_name} {father.family_name} id={father.id}")
Update a person (MERGE semantics — only supplied fields change):
client.persons.update(
father.id,
biography="Patrick emigrated to New York in 1847.",
verified=True,
)
Upload a profile picture and life-story media:
with open("patrick.jpg", "rb") as f:
client.persons.upload_image(father.id, f.read(), "image/jpeg")
# Life story media (plan-dependent: images for Individual/Family; also video/audio/PDF for Pro+)
with open("life-story.jpg", "rb") as f:
client.persons.upload_life_image(father.id, f.read(), "image/jpeg")
# Download (public endpoints — no auth required)
photo = client.persons.get_image(father.id)
life = client.persons.get_life_image(father.id)
Recording relationships¶
relationships.add() creates an edge between two persons:
from pyclann import RelationshipType, SiblingType
son = client.persons.create("Walsh", "Michael", "Male", trees=["walsh-family"])
# Father relationship
client.relationships.add(son.id, RelationshipType.FATHER, father.id)
# Sibling — sibling_type is required
client.relationships.add(
son.id,
RelationshipType.SIBLING,
sibling.id,
sibling_type=SiblingType.BROTHER,
)
# Spouse with dates
client.relationships.add(
father.id,
RelationshipType.SPOUSE,
mother.id,
spouse_from="1845-09-14",
spouse_to=None, # still married / unknown end date
)
Retrieve all relationships for a person:
rels = client.relationships.get(son.id)
print(rels.father) # list[Person]
print(rels.mother) # list[Person]
print(rels.siblings) # list[Person]
print(rels.spouse) # list[SpouseInfo]
Get the recursive family-tree view (2 generations of ancestors, plus children, spouses, siblings of the root):
node = client.relationships.get_family_tree(son.id)
print(node.first_name, node.family_name)
for parent in node.father:
print(" Father:", parent.first_name, parent.family_name)
Remove a relationship:
Life events¶
from pyclann import EventType
event = client.life_events.create(
father.id,
"Born in Galway",
EventType.BIRTH,
date="1820-06-01",
description="County Galway, Connacht province",
verified=True,
source_link="https://civil-records.ie/...",
created_by="alice",
)
# List events for a person
events = client.life_events.list(father.id)
# Update an event
client.life_events.update(event.id, verified=True, story="Long narrative in Markdown...")
# Delete
client.life_events.delete(event.id)
Research notes¶
Research notes are Markdown documents linked to one or more trees:
note = client.notes.create(
"Walsh Family Research",
trees=["walsh-family"],
description="Primary source research notes",
body="## Findings\n\nFound baptism record in Galway archives.",
is_shared=True, # visible to all team members with tree access
created_by="alice",
)
# Reply to a shared note
reply = client.notes.create_reply(
note.id,
"Also found a marriage record in Dublin.",
created_by="bob",
)
# File a note in a folder
folder = client.folders.create("Walsh Sources", created_by="alice")
client.notes.set_folder(note.id, folder.id)
AI chat¶
session = client.chat.create_session(
"Walsh Family History",
created_by="alice",
tree="walsh-family", # scope to a specific tree
)
client.chat.append_message(session.id, "user", "Who were Patrick Walsh's children?")
# (AI response stored separately by the clann-server)
messages = client.chat.list_messages(session.id)
for msg in messages:
print(f"[{msg.role}] {msg.content}")
Error handling reference¶
| Exception | Raised when |
|---|---|
ClannAuthError |
login() not called; 401 or 403 response |
ClannNotFoundError |
404 — resource does not exist |
ClannValidationError |
400 — invalid input |
ClannServerError |
5xx — server-side fault |
ClannError |
Base class; catches all of the above |