Kinship¶
Beyond parent/child pairs, Clade exposes the full set of relationships derivable from the tree — ancestors, descendants, siblings, and collateral lines like piblings, niblings, and cousins. Every term is gender-neutral throughout the codebase and this documentation.
Vocabulary¶
Term |
Meaning |
|---|---|
|
The direct ancestor of a node, one level up |
|
A direct descendant of a node, one level down |
|
Any node on the path from a node to the root |
|
Any node reachable downward from a node |
|
A node sharing the same parent |
|
A sibling of a node’s parent (gender-neutral for aunt/uncle) |
|
A child of a node’s sibling (gender-neutral for nephew/niece) |
|
A node sharing a common ancestor a fixed number of generations up |
|
The topmost node of a tree (no parent) |
|
A node with no children |
pibling and nibling aren’t Clade inventions — they’re established
gender-neutral terms already used in linguistics and social sciences, chosen
over aunt/uncle and nephew/niece to keep the vocabulary unambiguous
without defaulting to a gendered pair.
Direct relationships¶
Every method below is available both as a queryset call and as a manager call — use whichever fits the code you’re writing:
leaf.ancestors() # QuerySet, ordered by depth
leaf.descendants()
node.siblings() # excludes node itself
leaf.is_root # True if node has no parent
leaf.is_leaf # True if node has no children
leaf.root # the tree's root node
Category.objects.ancestors_of(leaf)
Category.objects.siblings_of(node)
siblings() returns an empty queryset for root nodes (no shared parent to
compare against) and for only-children — there’s nothing to treat as an
error case here, both are ordinary tree shapes.
Piblings and niblings¶
me.piblings() # siblings of my parent
aunt.niblings() # children of my siblings, from aunt's perspective
Both are pure compositions of siblings_of() — no new SQL is introduced for
either. piblings_of(node) is siblings_of(node.parent); niblings_of(node)
filters on parent__in=siblings_of(node). As of the current version, both
use a fixed degree only: there’s no “grand-pibling” (your grandparent’s
sibling) or equivalent extended reach. A configurable-degree variant is
planned but not yet implemented.
Cousins¶
Cousins don’t reduce to a fixed-degree relationship the way piblings and
niblings do — genealogical cousinage is normally described with two
parameters, degree and “removed”, and Clade deliberately does not implement
that full convention (see below). Instead, cousins_of() takes a single
degree parameter:
me.cousins() # degree=2 by default: "1st cousin"
me.cousins(degree=3) # "2nd cousin"
Category.objects.cousins_of(me, degree=2)
degree counts how many levels above node the shared ancestor sits.
degree=2 is the common case — nodes sharing a grandparent, excluding
siblings and piblings/niblings, which is what “1st cousin” means in
everyday usage. degree=1 is a degenerate case that returns the same
result as siblings_of().
Why “symmetric degree” instead of the genealogical convention¶
The full genealogical convention pins down a cousin relationship with two
numbers: degree (how many generations back to the shared ancestor) and
removed (how many generations apart the two people themselves are — “1st
cousin once removed” means the shared ancestor is a grandparent for one
person and a great-grandparent for the other). That two-parameter,
asymmetric comparison doesn’t reduce to the single-query pattern Clade uses
for ancestors_of/descendants_of — it would need a genuinely different
algorithm. cousins_of() instead uses symmetric degree: candidates must
sit at the same depth as node, sharing a common ancestor exactly
degree levels up from both. That’s a smaller vocabulary than full
genealogical cousinage, but it reuses the same path-distance primitive
already powering the rest of Clade’s queries, in a single SQL query on
either backend. The removed parameter is a possible future addition, not
something this version attempts.
A cardinality note, not a bug¶
cousins_of() computes a pairwise relationship — if A and B are cousins,
that holds in both directions. But the size of the result set isn’t
guaranteed to match between the two: on a tree where sibling branches have
different numbers of children, cousins_of(D, degree=2) and
cousins_of(F, degree=2) can return different numbers of results even
though D and F are cousins of each other in both directions. This is
expected behaviour under the symmetric-degree definition, not something to
work around.
Where to go next¶
Affinity — a different kind of relationship entirely: nodes that share attribute values with no hierarchical link between them at all.
API Reference — full API reference, generated from source.