synology_apm_repo.sdk.units.base module¶
Shared restorable-unit vocabulary. Every workload provider (Device, FS,
SaaS, …) implements UnitProvider against these same three types, so
the CLI/TUI never need to know which workload they’re looking at.
- class synology_apm_repo.sdk.units.base.UnitKind(value)¶
Bases:
EnumWhich restorable-unit kind a
Node/RestorableUnitis — drives the CLI/TUI’s default icon/preview routing.- DISK_IMAGE = 'disk_image'¶
- DISK_FILESYSTEM = 'disk_filesystem'¶
- DISK_FILE = 'disk_file'¶
- FILE = 'file'¶
- MAIL = 'mail'¶
- CONTACT = 'contact'¶
- CALENDAR_EVENT = 'calendar_event'¶
- DRIVE_ITEM = 'drive_item'¶
- SITE_ITEM = 'site_item'¶
- RAW_OBJECT = 'raw_object'¶
- TEAMS_CHAT_MESSAGE = 'teams_chat_message'¶
A Teams channel or Chat conversation’s own rendered transcript page — distinct from
RAW_OBJECT(RawObjectProvider’s unrelated raw diagnostic listing) and fromCATEGORY_GROUP(never a leaf’s own kind, only ever a container’sleaf_kind): this value is both a leaf’s own kind and everyTeamsChatProvidercontainer’sleaf_kind(root, each channel category) alike.
- CATEGORY_GROUP = 'category_group'¶
A container whose own children are always further containers, never a leaf — a SharePoint site’s “List” category (each child a List’s own group node) and a Calendar’s “My”/”Other Calendars” category (each child an individual calendar’s own group node). Shared across providers because the shape it describes is identical in both: Name+Created columns, and never
ColumnSpec.leaves_only(its children being containers is exactly what a folder listing should show, not filter out).
- class synology_apm_repo.sdk.units.base.FileState(value)¶
Bases:
EnumA disk-fs file’s cloud-sync/encryption state, carried as
Node.attrs["file_state"]— currently produced only byunits/content/disk_fs/’s NTFS/APFS backends (_ntfs.py/_apfs.py), not a concept every provider kind has. In priority order when more than one applies:CLOUD_ONLYalways wins overENCRYPTED— an evicted cloud placeholder has no local bytes at all, so its export fails regardless of whether it’s also EFS-encrypted.- NORMAL = 'normal'¶
- ENCRYPTED = 'encrypted'¶
- CLOUD_ONLY = 'cloud_only'¶
- class synology_apm_repo.sdk.units.base.ContentSource(*args, **kwargs)¶
Bases:
ProtocolThe one content-reading contract every restorable unit’s
open()returns — aDedupFile/ByteRangeViewsatisfies this shape already; anArtifactBuilder(assembled content —.eml,.ics, …) can implement it too without CLI/TUI ever needing to tell them apart.- property size: int | None¶
Deliberately a
@property, not a plain attribute. Protocol attributes are checked invariantly, so a plainint | Nonefield would reject a narrowerintattribute (e.g.ByteRangeView’ssize: int); a property satisfies this structurally regardless.Synchronous, even in this async SDK — every
DedupFile-backed implementer already knows its size without I/O.LazyArtifactis the one implementer that can’t; it reportsNoneuntil its artifact is assembled, whichint | Noneexists for.
- async read(offset=0, length=None)¶
Read
lengthbytes starting atoffset(default: fromoffsetto the end). A request extending past the content’s own end — includingoffsetstarting at or past it — returns only the bytes that exist, never an error; a genuinely empty result is a normal, valid read, not a failure.- Raises:
ValueError –
offsetorlengthis negative.
- stream(block=8388608)¶
- async export_to(dst, *, sparse=True, progress=None)¶
- property supports_concurrent_export: bool¶
Whether
export_to()acceptsmax_concurrent_reads/max_concurrent_opensmeaningfully — true only for a source with a bucket concept to spread reads across (DedupFile/ByteRangeView,VirtualDiskContentSource). Declared explicitly here, not inferred byisinstanceagainst a concrete class, so every implementation states its own capability directly.
- class synology_apm_repo.sdk.units.base.Node(ref, name, is_leaf, kind=None, size=None, attrs=<factory>)¶
Bases:
objectOne entry in a provider’s browsable tree; leaves additionally carry
kind/size.- Variables:
ref (synology_apm_repo.sdk.units.node_ref.NodeRef) – This node’s canonical reference.
name (str) – Display name.
is_leaf (bool) – Whether this node is a restorable unit rather than a container.
kind (synology_apm_repo.sdk.units.base.UnitKind | None) – The kind of restorable unit, when known.
size (int | None) – Byte size, when known.
attrs (dict[str, Any]) – Display metadata (sender, mtime, path, …) — CLI/TUI show only
name/attrsin the default, non-diagnostic mode; internal identifiers a provider needs for its own bookkeeping should go in a provider-privateattrskey, not become part of this public shape.
- synology_apm_repo.sdk.units.base.node_file_state(node)¶
node.attrs["file_state"]narrowed to a realFileState, defaulting toNORMALfor a node with no such concept (most provider kinds) or a malformed value —Node.attrsis an untypeddict[str, Any], so this is the one narrowing every CLI/TUI render site needs, instead of repeating the same check at each of them.
- synology_apm_repo.sdk.units.base.node_modified_time(node)¶
node.attrs["mtime"]narrowed to a real, timezone-awaredatetime—Nonefor a node with no such concept (most provider kinds don’t set this key at all) or a malformed value. Mirrorsnode_file_state’s own narrow-with-default shape; unlike that one there is no meaningful default to fall back to, so this returnsNoneoutright rather than inventing a timestamp.
- synology_apm_repo.sdk.units.base.node_leaf_kind(node)¶
node.attrs["leaf_kind"]narrowed to a realUnitKind—Nonefor a node with no such concept. Set on every container node aSaasWorkloadProvider-based provider builds (root and every group alike, all sharing the oneUnitKinditsSaasWorkloadConfig.leaf_kinddeclares), so a caller can resolve what kind of leaf a given folder holds directly from that folder’s ownNode— at any depth, with no child inspection needed and no ambiguity for a folder with zero children. UnlikeNode.kind(which a leaf carries directly), this describes a container’s own children’s kind, not the node itself.
- synology_apm_repo.sdk.units.base.node_kind_label(node)¶
node.kind.value, or a generic"folder"/"item"fallback for aNodewhosekindisn’t known (most container nodes, and a handful of leaf kinds no provider ever sets) — the one place every CLI/TUI render site derives a printable kind label from a resolved item-treeNode, so no two of them can disagree on it.
- synology_apm_repo.sdk.units.base.mtime_from_epoch(epoch)¶
The write-side counterpart of
node_modified_time– converts a provider’s own raw epoch-seconds catalog value (FS’sfile_mtime, Drive’smtime, …) into what that accessor reads back.Noneboth whenepochitself isNone(nothing to convert) and when it’s outsidedatetime’s own representable range (OverflowError/OSError/ValueError) – a corrupt-catalog value must degrade this one node’s Modified cell to blank rather than failing its whole containing folder’s listing.
- synology_apm_repo.sdk.units.base.mtime_attr(raw)¶
mtime_from_epoch(as_int(raw)), narrowing aTable.select()row’s own untypedobject | Nonevalue first – shared by every provider whoseextra_attrs/_group_attrspopulatesattrs["mtime"]straight from one raw, optional epoch-seconds column, rather than each repeating the sameas_int-then-convert pair (and its ownis not Noneguard, needed only becauseas_intitself raises onNonerather than accepting it).
- synology_apm_repo.sdk.units.base.mtime_attrs(raw, key='mtime')¶
{key: mtime_attr(raw)}, or{}when that’sNone– the one-lineattrs.update(...)/returnshape everyextra_attrs/group_attrscallback wanting a single epoch-derived attr reduces to, instead of each spelling out its ownif mtime is not None: ...guard aroundmtime_attrdirectly.keydefaults to the common case ("mtime"); Calendar’s ownevent_start/event_endare the one caller needing something else.
- class synology_apm_repo.sdk.units.base.RestorableUnit(ref, name, is_leaf, kind=None, size=None, attrs=<factory>, content=None)¶
Bases:
NodeA leaf
Nodeplus a way to actually read its content.contentis already-constructed (cheap for aDedupFile— it does no I/O until first read) rather than a lazy thunk, keeping this a plain dataclass instead of needing its own__post_init__wiring.- content: ContentSource | None = None¶
- open()¶
- synology_apm_repo.sdk.units.base.diagnostic_node(ref, name, attrs)¶
One synthetic listing node standing in for a provider’s own degrade-instead-of-fail case (
units/device_pcps.py’s missing-fids node,units/device_disk_fs.py’s no-filesystem-recognized node, …) — theNode(is_leaf=True, kind=UnitKind.FILE, ...)shape every one of them shares; onlyattrs(which_kindmarks it as, and whatunit()needs to raise the right diagnosticNotFoundErrorlater) actually differs between them.
- synology_apm_repo.sdk.units.base.node_is_diagnostic(node)¶
Whether
nodeis adiagnostic_node()placeholder rather than a real, restorable file — the one check every CLI/TUI render site needs so a placeholder isn’t shown indistinguishably from real content (it looks identical otherwise: samekind, sameis_leaf).
- synology_apm_repo.sdk.units.base.not_restorable(kind, ref)¶
Raise the standard
ValueErroreveryUnitProvider.unitimplementation raises for a resolved-but-contentless node/item.
- synology_apm_repo.sdk.units.base.dir_first_sort_key(is_dir, name)¶
The child-ordering policy every browsable file/folder-tree
UnitProviderapplies: containers before leaves, then each group alphabetically by name (byte/codepoint comparison, no case folding). Use as asorted(..., key=...)key for a provider that sorts in Python;dir_first_order_byexpresses the same policy as SQL for a provider that pushes ordering down into its own query instead.
- synology_apm_repo.sdk.units.base.dir_first_order_by(is_dir_sql, order_by)¶
The same containers-before-leaves-then-name policy as
dir_first_sort_key, expressed as a SQLORDER BYexpression.is_dir_sqlis a boolean SQL expression true for a container row;order_byis the already-built name(+tiebreaker) clause ranking rows within each group.
- synology_apm_repo.sdk.units.base.disk_fs_containers_before_leaves(nodes)¶
Stable-partitions an already-ordered node list into containers (
is_leaf=False) before leaves, each group keeping its incoming relative order — for a disk-image node interleaved with its own “(filesystem)” sibling (device_disk_fs.DiskFsSibling.root_node), whose pairwise order already comes from something more meaningful than name (a disk index, a databaseORDER BY), wheredir_first_sort_key’s alphabetical secondary key would undo it.
- synology_apm_repo.sdk.units.base.paginate(items, offset, limit)¶
Slice
items[offset:offset+limit](open-ended whenlimitisNone) — the sameoffset/limitcontractUnitProvider.childrentakes, for a provider whose own listing is already a small, fully-materialized Python sequence (group-level listings, an in-memory tree walk) rather than something worth pushing down into a real SQLLIMIT/OFFSET— unlikeunits/fs.py’schildren(), which pushes ordering and limiting down into SQL instead, a different mechanism achieving the same contract. Lives here, not in one provider subpackage, so every provider family (units/file_map_tree.py,units/saas/raw_object.py, …) can share one implementation rather than each hand-rolling this same two-line slice independently.
- class synology_apm_repo.sdk.units.base.UnitProvider(*args, **kwargs)¶
Bases:
ProtocolOne workload version’s browsable tree.
rootis sync (every implementation resolves its state up front, in an async constructor);childrenandunitare async. This is a property of the method, uniform across every implementation, even ones that happen to do no I/O in a given body.No
close()here: providers that own sqlite sources expose their ownasync def close()instead, without this Protocol requiring one of every implementation.- root()¶
- async children(node, offset=0, limit=None)¶
- async unit(node)¶
- class synology_apm_repo.sdk.units.base.ClosableUnitProvider(*args, **kwargs)¶
Bases:
UnitProvider,ProtocolA
UnitProviderthat owns a realSqliteSource/aiosqliteconnection and must be closed once done.Every concrete provider that owns one (
FsProvider,DeviceProvider,RawObjectProvider,SaasWorkloadProvider,CompositeSaasProvider) implements this in addition to plainUnitProvider; a provider with no such connection (FileMapTreeProvider) implements only the latter. The__aenter__/__aexit__pair makesasync withthe ordinary way to hold one, closing it on every exit path including an exception; a caller that already holds an instance some other way can still justawaitclosedirectly.- async close()¶
- class synology_apm_repo.sdk.units.base.SupportsDirectRefLookup(*args, **kwargs)¶
Bases:
ProtocolImplemented only by a provider whose tree addresses every node by a single, depth-independent key (Drive’s
item_id— seetree_strategy.RecursiveTree), for whichfind_node/find_path_with_childrencannot use their usual prefix-guided descent (aNodeRef’sextra_segmentsdoesn’t grow with depth for such a provider, so no child’s ref is ever a longer prefix of the target’s than any other’s).resolve_extralooks a node up directly, without visiting any other node in the tree.parent_ofwalks one step toward the root (Noneat the version root) — used only to rebuild the ancestor chain a caller like the TUI’s goto-ref needs; each ancestor’s own children are then fetched the ordinary way, oneUnitProvider.childrencall per ancestor.- async resolve_extra(extra_segments)¶
- async parent_of(node)¶