synology_apm_repo.sdk.units.content.saas_artifact module

LazyArtifact: the ContentSource implementation shared by every application-layer artifact this project builds (Calendar’s .ics, Mail’s .eml, Contact’s CSV) — each cheap to construct a node for, expensive to actually assemble, and once assembled just a small, fully in-memory blob of bytes. CalendarProvider/ MailProvider/ContactProvider each supply only the one thing that’s actually different: the build callback itself.

Teams chat/channel content is the one known exception to “small”: render_channel_html (saas_teams_chat.py) holds an entire rendered channel/chat history in memory as one string.

parse_meta_json is a second, smaller shared piece those same build callbacks (and units/saas/site.py’s own item-_assemble, one layer up) each need: every one of them starts by parsing a fetched META object’s raw bytes as JSON before touching its fields.

synology_apm_repo.sdk.units.content.saas_artifact.parse_meta_json(meta_bytes, label, *, ref=None)

Parses meta_bytes as JSON, raising DataCorruptError(f"{label} did not parse as JSON: ...", ref=ref) instead of letting a bare json.JSONDecodeError propagate — the identical try/except every META-object JSON parse in this project needs (Mail, Contact, Calendar, SharePoint Site). label is the caller’s own already-formatted “<item kind> <id!r> META”-shaped prefix, so each keeps its own established wording/ordering rather than this function imposing one.

Returns dict[str, Any] — real META JSON is always an object at the top level, but individual field values are still Any: every caller immediately narrows them with its own .get()/isinstance checks (the data is untrusted on-disk content, never assumed-shaped), the same way it already would against a bare json.loads() result.

class synology_apm_repo.sdk.units.content.saas_artifact.LazyArtifact(build)

Bases: object

Implements ContentSource around an async build() -> bytes callback, awaited at most once and cached on the first read/stream. Assembly failures (e.g. a META object shaped in a way the caller doesn’t recognize) surface as whatever exception build itself raises, the first time any of read/stream/export_to is actually awaited — constructing a RestorableUnit around a LazyArtifact never does I/O or raises on its own.

property size: int | None

None until the artifact has actually been assembled — a synchronous property, but assembling requires await, so a caller that needs the real length must first read/stream/export the content.

property supports_concurrent_export: bool

Always False — an assembled artifact is a single in-memory blob with no bucket concept to spread reads across.

async read(offset=0, length=None)
async stream(block=8388608)
async export_to(dst, *, sparse=True, progress=None)