synology_apm_repo.sdk.units.saas.contact module

ContactProvider: M365/GWS Contact via contact_table, built as a SaasWorkloadProvider + SyntheticGroupedTree config.

An empty contact_table (a tenant with zero contacts) is a legitimate state, not a gap — ContactProvider still constructs successfully and finds a real, empty table.

The two platforms differ enough that they aren’t one shared code path:

  • M365 (contact_table has parent_folder_id — a real, single-parent folder hierarchy resolved to names via _m365_contact_folder_names): metadata JSON is {"version":"1.0","client_metadata":{...Graph API contact fields, camelCase...},"contact_type":"Contact"}; no photo concept at all. Exported as CSV — the only portable format M365 Contact has (no vCard, no PST-equivalent); see build_contact_csv.

  • GWS (contact_table has no folder column — contacts instead belong to zero or more groups, an M:N relationship, not a hierarchy): metadata JSON is {"version":"2.0","client_metadata":{...People API Person fields...},"photo_object_id":...} (the last three keys — photo_size/photo_hash/photo_object_id — only present when the contact has a photo). Surfaced as raw JSON. Group membership is a real many-to-many relationship, not synthesized — see _gws_contact_groups — including Google’s own built-in "myContacts" system group alongside any user-named ones.

Tree: a synthetic top-level grouping — M365’s real, name-resolved parent_folder_id, a single synthetic “Contacts” bucket for GWS — then a flat leaf per contact (the “grouped flat list” strategy, SyntheticGroupedTree).

synology_apm_repo.sdk.units.saas.contact.CONTACT_CONFIG = SaasWorkloadConfig(root_name='Contacts', leaf_kind=<UnitKind.CONTACT: 'contact'>, tables=('contact_table',), tree_factory=<function _build_tree>, assemble=<function _assemble>, object_names={'contact_table': ('contact_db',)}, extra_attrs=<function _contact_extra_attrs>, group_attrs=<function SaasWorkloadConfig.<lambda>>, leaf_size=<function SaasWorkloadConfig.<lambda>>)

SaasWorkloadConfig behind ContactProvider — M365 contacts have a real, single-parent folder hierarchy (parent_folder_id); GWS contacts instead belong to zero or more groups, an M:N relationship with no folder column at all.

async synology_apm_repo.sdk.units.saas.contact.ContactProvider(repo, version, saas_streams, *, shared=None)

Constructor-style factory over CONTACT_CONFIG — callable exactly like a constructor (await ContactProvider(repo, version, saas_streams)), with an optional shared context passed straight through to SaasWorkloadProvider.create for M365’s multi-candidate USER_EXCHANGE/GROUP_EXCHANGE dispatch.