@@ -503,6 +503,80 @@ def lookup_codec(codec_spec: str) -> tuple[Codec, str | None]:
503503 raise DataJointError (f"Codec <{ type_name } > is not registered. " "Define a Codec subclass with name='{type_name}'." )
504504
505505
506+ # =============================================================================
507+ # Decode Helper
508+ # =============================================================================
509+
510+
511+ def decode_attribute (attr , data , squeeze : bool = False ):
512+ """
513+ Decode raw database value using attribute's codec or native type handling.
514+
515+ This is the central decode function used by all fetch methods. It handles:
516+ - Codec chains (e.g., <blob@store> → <hash> → bytes)
517+ - Native type conversions (JSON, UUID)
518+ - External storage downloads (via config["download_path"])
519+
520+ Args:
521+ attr: Attribute from the table's heading.
522+ data: Raw value fetched from the database.
523+ squeeze: If True, remove singleton dimensions from numpy arrays.
524+
525+ Returns:
526+ Decoded Python value.
527+ """
528+ import json
529+ import uuid as uuid_module
530+
531+ import numpy as np
532+
533+ if data is None :
534+ return None
535+
536+ if attr .codec :
537+ # Get store if present for external storage
538+ store = getattr (attr , "store" , None )
539+ if store is not None :
540+ dtype_spec = f"<{ attr .codec .name } @{ store } >"
541+ else :
542+ dtype_spec = f"<{ attr .codec .name } >"
543+
544+ final_dtype , type_chain , _ = resolve_dtype (dtype_spec )
545+
546+ # Process the final storage type (what's in the database)
547+ if final_dtype .lower () == "json" :
548+ data = json .loads (data )
549+ elif final_dtype .lower () in ("longblob" , "blob" , "mediumblob" , "tinyblob" ):
550+ pass # Blob data is already bytes
551+ elif final_dtype .lower () == "binary(16)" :
552+ data = uuid_module .UUID (bytes = data )
553+
554+ # Apply decoders in reverse order: innermost first, then outermost
555+ for codec in reversed (type_chain ):
556+ data = codec .decode (data , key = None )
557+
558+ # Squeeze arrays if requested
559+ if squeeze and isinstance (data , np .ndarray ):
560+ data = data .squeeze ()
561+
562+ return data
563+
564+ # No codec - handle native types
565+ if attr .json :
566+ return json .loads (data )
567+
568+ if attr .uuid :
569+ import uuid as uuid_module
570+
571+ return uuid_module .UUID (bytes = data )
572+
573+ if attr .is_blob :
574+ return data # Raw bytes
575+
576+ # Native types - pass through unchanged
577+ return data
578+
579+
506580# =============================================================================
507581# Auto-register built-in codecs
508582# =============================================================================
0 commit comments