synology_apm_repo.sdk.units.dispatch module

Provider dispatch. Lives in units, not catalog, so catalog never has to import units and form a cycle. target_type in {VM,PC,PS} -> DeviceProvider; FS -> FsProvider; SaaS ({M365,GW}) routes through saas_provider_for, which picks an application-layer provider by the owning Workload’s catalog-derived sub_type and degrades to RawObjectProvider on recognition failure.

synology_apm_repo.sdk.units.dispatch.SUPPORTED_TARGET_TYPES = frozenset({TargetType.FS, TargetType.PC, TargetType.PS, TargetType.VM})

target_type values provider_for alone can build a provider for — VM/PC/PS/FS only. SaaS dispatch needs the owning Workload (for sub_type), so it lives in the separate saas_provider_for below; see SUPPORTED_SAAS_SUB_TYPES for that axis’s equivalent. Membership here doesn’t guarantee every individual version resolves — a specific PC/PS version can still have every disk fragment unresolvable at runtime (device_pcps.PcpsDiskTree’s own diagnostic-node path).

synology_apm_repo.sdk.units.dispatch.SUPPORTED_SAAS_SUB_TYPES = frozenset({'CALENDAR', 'CONTACT', 'DRIVE', 'GROUP_EXCHANGE', 'MAIL', 'SITE', 'TEAMS', 'TEAM_DRIVE', 'USER_CHAT', 'USER_DRIVE', 'USER_EXCHANGE'})

Workload.sub_type values saas_provider_for can attempt an application-layer provider for. This does not guarantee every individual version of a workload with a recognized sub_type succeeds (a specific version can still degrade to raw at runtime) — same caveat SUPPORTED_TARGET_TYPES already carries for PC/PS.

synology_apm_repo.sdk.units.dispatch.is_supported(workload)

Whether workload has a chance at an application-layer provider (VM/PC/PS/FS via provider_for, or a recognized SaaS sub_type via saas_provider_for) — a plain, no-I/O check over SUPPORTED_TARGET_TYPES/SUPPORTED_SAAS_SUB_TYPES, for callers (e.g. the CLI’s doctor command, via Repository.workload_is_supported) that want to report “not yet supported” without constructing a provider. Carries the same caveat those two constants do: True doesn’t guarantee every individual version actually resolves — a specific version can still fail construction or degrade to a raw fallback at runtime.

async synology_apm_repo.sdk.units.dispatch.provider_for(repo, version)

Return the right ClosableUnitProvider for version, based on its target_type alone (VM/PC/PS/FS only).

Async to match UnitProvider’s own construction uniformity, not because this dispatch itself does I/O: the target_type branch is a pure string comparison, and both DeviceProvider.create and FsProvider.__init__ construct with no I/O.

Raises:

UnsupportedDataFormatError – version.target_type is a SaaS type (M365/GW) — those need the owning Workload too (for sub_type); call saas_provider_for instead.

async synology_apm_repo.sdk.units.dispatch.saas_provider_for(repo, workload, version, saas_streams, *, object_db_id=None)

Route one SaaS Version to its application-layer provider(s), by the owning Workload.sub_type. Tries every candidate in _SAAS_SUB_TYPE_CANDIDATES rather than stopping at the first match — M365’s USER_EXCHANGE/GROUP_EXCHANGE can genuinely have Mail, Contact, and Calendar all present in the same version at once. Zero matches degrades to RawObjectProvider; exactly one is returned directly; more than one is wrapped in CompositeSaasProvider so every match stays reachable as a sibling top-level group.

M365’s USER_EXCHANGE/GROUP_EXCHANGE bundle Mail/Contact/ Calendar under one sub_type; GWS’s own account instead has independent workloads per app type ("MAIL"/"CONTACT"/ "CALENDAR"), so its candidate lists carry only one entry each.

saas_streams — the caller’s shared SaasStreamCache (built against this same repo, never one built independently — see api.repository._OpenCatalog) — is threaded into every candidate and the fallback alike, so a stream this call opens is reused by every other version of the same stream a later call resolves too, not just this call’s own sibling candidates.

object_db_id only reaches the fallback RawObjectProvider construction — the application-layer candidates above resolve their own service DB internally.

Async because each candidate factory(...) genuinely reads (opens saas_obj, resolves its object-name index) to decide whether it recognizes this version at all — except when more than one candidate is offered for this sub_type (USER_EXCHANGE/GROUP_EXCHANGE today, per _SAAS_SUB_TYPE_CANDIDATES), in which case that read happens exactly once, up front, via resolve_shared_saas_context, and every candidate reuses the result instead of each re-resolving the same (repo, version)’s saas_obj/object-name index for itself. A single-candidate sub_type gets shared=None here, since there’s only one candidate to share the read with.

async synology_apm_repo.sdk.units.dispatch.raw_fallback_provider_for(repo, version, saas_streams, *, object_db_id=None)

The degradation axis, made directly reachable: diagnostic tooling, the TUI’s d-mode override, or saas_provider_for itself (when no application-layer provider recognizes a version) can get a SaaS version’s raw object-name-index tree directly. Kept separate from provider_for’s dispatch — that one stays a hard refusal for a bare Version with no Workload, keeping the diagnostic-only distinction visible at the type level.

Async because RawObjectProvider.create is — it opens the version’s saas_obj (via saas_streams) and resolves its object-name index.