1515Serialization options:
1616- save_to_zip() / load_from_zip(): Single self-contained zip file
1717- save() / load(): File-based asset store with deduplication
18+
19+ Checksum Scheme:
20+ Packable checksums are computed from the JSON representation of extracted data.
21+ This makes checksum recreation straightforward outside this library.
22+
23+ Format: SHA256 of compact JSON: {"data":<data>,"json_schema":<schema>}
24+ Keys are sorted, no whitespace (single line).
25+
26+ The `data` dict contains $ref entries (e.g. {"$ref":"abc123..."}) pointing
27+ to asset checksums, so the packable checksum transitively covers all binary
28+ content without embedding the actual bytes.
29+
30+ To recreate a checksum externally:
31+ import hashlib, json
32+ payload = {"data": packable_data, "json_schema": schema}
33+ compact_json = json.dumps(payload, sort_keys=True, separators=(',', ':'))
34+ checksum = hashlib.sha256(compact_json.encode()).hexdigest()
1835"""
1936
2037import time
@@ -68,9 +85,33 @@ class ExtractedPackable(BaseModel):
6885
6986 data : dict [str , Any ] = Field (..., description = "Serializable dict with primitive fields and checksum refs for arrays" )
7087 json_schema : Optional [dict [str , Any ]] = Field (default = None , description = "JSON Schema with encoding info" )
71- checksum : Optional [str ] = Field (default = None , description = "Content checksum of the source Packable" )
7288 assets : dict [str , bytes ] = Field (default_factory = dict , exclude = True , description = "Map of checksum -> encoded bytes for all arrays" )
7389
90+ @cached_property
91+ def checksum (self ) -> str :
92+ """SHA256 checksum computed from data and json_schema.
93+
94+ Checksum Format:
95+ SHA256 of compact JSON: {"data":<data>,"json_schema":<schema>}
96+ Keys are sorted, no whitespace (single line).
97+
98+ Why JSON-based:
99+ The data dict contains $ref entries pointing to asset checksums,
100+ so this checksum transitively covers all array/binary content.
101+ This format makes checksum recreation straightforward outside meshly:
102+
103+ import hashlib, json
104+ payload = {"data": extracted_data, "json_schema": schema}
105+ compact_json = json.dumps(payload, sort_keys=True, separators=(',', ':'))
106+ checksum = hashlib.sha256(compact_json.encode()).hexdigest()
107+
108+ Returns:
109+ SHA256 hex digest string
110+ """
111+ payload = {"data" : self .data , "json_schema" : self .json_schema }
112+ json_bytes = orjson .dumps (payload , option = orjson .OPT_SORT_KEYS )
113+ return ChecksumUtils .compute_bytes_checksum (json_bytes )
114+
74115 def extract_checksums (self ) -> list [str ]:
75116 """Extract all $ref checksums from a serialized data dict.
76117
@@ -209,7 +250,6 @@ def load_extracted(self, key: str) -> "ExtractedPackable":
209250 return ExtractedPackable (
210251 data = extracted_data ["data" ],
211252 json_schema = extracted_data .get ("json_schema" ),
212- checksum = extracted_data .get ("checksum" ),
213253 )
214254
215255 def extracted_exists (self , key : str ) -> bool :
@@ -298,7 +338,7 @@ def extract(self) -> "ExtractedPackable":
298338 Results are cached for efficiency. Subsequent calls return the cached result.
299339
300340 Returns:
301- ExtractedPackable with metadata (data + schema) and binary assets.
341+ ExtractedPackable with metadata (data + schema + checksum ) and binary assets.
302342 """
303343 if self ._cached_extract is not None :
304344 return self ._cached_extract
@@ -310,11 +350,13 @@ def extract(self) -> "ExtractedPackable":
310350
311351 assert isinstance (extracted_result .value , dict ), "Extracted value must be a dict for Packable models"
312352
313- self . _cached_extract = ExtractedPackable (
353+ extracted = ExtractedPackable (
314354 data = extracted_result .value ,
315355 json_schema = type (self ).cached_json_schema (),
316356 assets = extracted_result .assets ,
317357 )
358+
359+ self ._cached_extract = extracted
318360 return self ._cached_extract
319361
320362 @cached_property
@@ -351,8 +393,22 @@ def encode(self) -> bytes:
351393
352394 @cached_property
353395 def checksum (self ) -> str :
354- """SHA256 checksum of this Packable's encoded bytes (cached)."""
355- return ChecksumUtils .compute_bytes_checksum (self ._encoded )
396+ """SHA256 checksum of this Packable's extracted JSON representation (cached).
397+
398+ Checksum Format:
399+ SHA256 of compact JSON: {"data":<extracted_data>,"json_schema":<schema>}
400+ Keys are sorted, no whitespace (single line).
401+
402+ The data dict contains $ref entries pointing to asset checksums (e.g.,
403+ {"$ref":"abc123..."}), so this checksum transitively covers all binary content.
404+
405+ To recreate this checksum outside meshly:
406+ import hashlib, json
407+ payload = {"data": packable_data, "json_schema": schema}
408+ compact_json = json.dumps(payload, sort_keys=True, separators=(',', ':'))
409+ checksum = hashlib.sha256(compact_json.encode()).hexdigest()
410+ """
411+ return self .extract ().checksum
356412
357413 def set_checksum (self , checksum : str ) -> None :
358414 """Pre-populate the cached checksum to avoid re-encoding."""
@@ -392,9 +448,8 @@ def decode(
392448 )
393449 result = cls .reconstruct (extracted , array_type = array_type )
394450
395- # Cache to ensure consistent checksums on re-encoding
451+ # Cache for efficiency
396452 result ._cached_encode = buf
397- result .set_checksum (ChecksumUtils .compute_bytes_checksum (buf ))
398453 result ._cached_extract = extracted
399454 return result
400455
@@ -445,7 +500,7 @@ def reconstruct(
445500 resolved_data = SchemaUtils .resolve_from_class (cls , extracted .data , asset_provider , array_type )
446501 result = cls (** resolved_data )
447502
448- if extracted . checksum and isinstance (result , Packable ):
503+ if isinstance (result , Packable ):
449504 result .set_checksum (extracted .checksum )
450505 return result
451506
0 commit comments