angee.parties.managers
Managers that own the directory-sync write path for parties.
A directory backend parses a source into neutral ParsedContact rows; these managers turn one into a Party (a Person) and its Handle / PartyHandle / Address rows. A contact is keyed by its source UID within its folder (the idempotent (folder, source_uid) upsert), handles dedupe on (platform, value), and handle_count plus the resolved Handle.party are maintained here in the same transaction — so every directory source shares one write path (the map lives on the models, not in each backend) and a re-sync converges instead of duplicating. The sync runs under system_context, so created_by is set explicitly to the directory owner.
HandleManager
class HandleManager(AngeeManager)Factory + upsert for handles (the contact-point write path).
upsert
def upsert(*,
platform: str,
value: str,
owner_id: Any = None,
**fields: Any) -> AnyGet-or-create a handle by its (platform, value) dedup key, refreshing display fields.
PartyHandleManager
class PartyHandleManager(AngeeManager)Owns the confidence link between a party and a handle, and the resolution.
link
def link(party: Any,
handle: Any,
*,
confidence: float = 1.0,
source: str = "manual",
owner_id: Any = None) -> AnyLink handle to party with confidence, then resolve the handle's owner.
Resolution only re-runs when the link is new or the handle's owner is not already this party, so a re-sync of an unchanged contact does no extra work.
resolve
def resolve(handle: Any) -> NoneMaterialise handle.party to the highest-confidence, non-dismissed link.
The resolution ordering (-is_confirmed, -confidence) is the contacts rule: a human-confirmed link wins, then the strongest score. A handle with no surviving link is left unowned.
PartyManager
class PartyManager(AngeeManager)Factory for parties, including the idempotent directory-sync ingest.
ingest_contact
def ingest_contact(parsed: ParsedContact, *, folder: Any,
owner_id: Any) -> AnyUpsert a person and its handles/addresses from one parsed contact.
Keyed on (folder, source_uid) so a re-sync updates the same row instead of forking a duplicate, and the whole contact is written in one transaction so a partial card is never half-applied. Emails/phones still upsert as shared Handle rows and link to the person, but the person's identity is the source UID, not handle overlap. A contact with no source_uid has no stable key and is skipped — without it the (folder, "") upsert would collapse every keyless card onto one row.
purge_missing
def purge_missing(*, folder: Any, keep_uids: set[str]) -> intDelete the folder's synced parties whose source UID is no longer present.
This is how a contact deleted on the source is mirrored locally: anything in folder carrying a source_uid not in keep_uids is removed (the MTI child cascades with its parent). A handle shared with a surviving party is re-resolved afterwards, since its link to the deleted party cascades away.