@@ -229,9 +229,29 @@ def _object_roots(self) -> set:
229229 """Return all registered object-root full keys."""
230230 return set (self ._objects_registry ().keys ())
231231
232+ def _probed_object_roots (self ) -> set :
233+ """Return object roots discovered from physical CTable manifests."""
234+ roots = set ()
235+ candidates = set (self .map_tree .keys ()) | set (self ._estore .keys ())
236+ for key in candidates :
237+ if not key .endswith ("/_meta" ):
238+ continue
239+ root = key [: - len ("/_meta" )]
240+ if not root :
241+ # A manifest at /_meta marks the TreeStore itself as a CTable
242+ # backing store; it is not an inline object root to collapse.
243+ continue
244+ if self ._probe_object_info (root ) is not None :
245+ roots .add (root )
246+ return roots
247+
248+ def _known_object_roots (self ) -> set :
249+ """Return registered plus physically probed object-root full keys."""
250+ return self ._object_roots () | self ._probed_object_roots ()
251+
232252 def _effective_object_roots (self ) -> set :
233253 """Object root keys relative to the current view (subtree or root)."""
234- all_roots = self ._object_roots ()
254+ all_roots = self ._known_object_roots ()
235255 if not self .subtree_path :
236256 return all_roots
237257 result = set ()
@@ -386,7 +406,7 @@ def __setitem__(
386406
387407 # Block overwriting an existing object root with a plain value
388408 full_key = self ._translate_key_to_full (key )
389- if self ._object_info (full_key ) is not None :
409+ if ( self ._object_info (full_key ) or self . _probe_object_info ( full_key ) ) is not None :
390410 raise ValueError (
391411 f"'{ key } ' is an object root (e.g. CTable). "
392412 f"Delete it first with `del ts['{ key } ']` before assigning a new value."
@@ -421,7 +441,7 @@ def _set_ctable_object(self, key: str, value: blosc2.CTable) -> None:
421441 full_key = self ._translate_key_to_full (key )
422442
423443 # Raise if already exists as object root (no silent replace)
424- if self ._object_info (full_key ) is not None :
444+ if ( self ._object_info (full_key ) or self . _probe_object_info ( full_key ) ) is not None :
425445 raise ValueError (
426446 f"'{ key } ' already exists as an object root. Delete it first with `del ts['{ key } ']`."
427447 )
@@ -531,7 +551,7 @@ def __delitem__(self, key: str) -> None:
531551 full_key = self ._translate_key_to_full (key )
532552
533553 # --- Object root deletion ---
534- if self ._object_info (full_key ) is not None :
554+ if ( self ._object_info (full_key ) or self . _probe_object_info ( full_key ) ) is not None :
535555 self ._delete_object_subtree (full_key )
536556 return
537557
@@ -545,8 +565,14 @@ def __delitem__(self, key: str) -> None:
545565 # Regular node / subtree deletion
546566 key_exists_as_data = super ().__contains__ (full_key )
547567 descendants = self .get_descendants (key )
548-
549- if not key_exists_as_data and not descendants :
568+ prefix = full_key + "/" if full_key != "/" else "/"
569+ object_roots_to_delete = sorted (
570+ [root for root in self ._known_object_roots () if root .startswith (prefix )],
571+ key = len ,
572+ reverse = True ,
573+ )
574+
575+ if not key_exists_as_data and not descendants and not object_roots_to_delete :
550576 raise KeyError (f"Key '{ key } ' not found" )
551577
552578 keys_to_delete = []
@@ -557,8 +583,18 @@ def __delitem__(self, key: str) -> None:
557583 if super ().__contains__ (full_desc ):
558584 keys_to_delete .append (descendant )
559585
586+ for object_root in object_roots_to_delete :
587+ self ._delete_object_subtree (object_root )
588+
560589 for k in keys_to_delete :
561- super ().__delitem__ (self ._translate_key_to_full (k ))
590+ full_desc = self ._translate_key_to_full (k )
591+ if super ().__contains__ (full_desc ):
592+ super ().__delitem__ (full_desc )
593+
594+ # Remove stale registry entries for any nested objects that were deleted as plain descendants.
595+ for root in list (self ._object_roots ()):
596+ if root .startswith (prefix ):
597+ self ._unregister_object (root )
562598
563599 def _delete_object_subtree (self , full_key : str ) -> None :
564600 """Delete all physical leaves under *full_key* and unregister it."""
@@ -614,7 +650,11 @@ def __contains__(self, key: str) -> bool:
614650 if self ._is_object_internal_key (key ):
615651 return False
616652 full_key = self ._translate_key_to_full (key )
617- return super ().__contains__ (full_key ) or self ._object_info (full_key ) is not None
653+ return (
654+ super ().__contains__ (full_key )
655+ or self ._object_info (full_key ) is not None
656+ or self ._probe_object_info (full_key ) is not None
657+ )
618658 except ValueError :
619659 return False
620660
@@ -679,6 +719,15 @@ def items(self) -> Iterator[tuple[str, NDArray | C2Array | SChunk | TreeStore]]:
679719 for key in self .keys ():
680720 yield key , self [key ]
681721
722+ def values (
723+ self ,
724+ ) -> Iterator [
725+ NDArray | C2Array | SChunk | blosc2 .ObjectArray | blosc2 .BatchArray | blosc2 .CTable | TreeStore
726+ ]:
727+ """Return values in the current subtree view, with object roots collapsed."""
728+ for key in self .keys ():
729+ yield self [key ]
730+
682731 def get_children (self , path : str ) -> list [str ]:
683732 """Get direct children of a given path.
684733
@@ -815,7 +864,11 @@ def walk(self, path: str = "/", topdown: bool = True) -> Iterator[tuple[str, lis
815864 child_rel_path = path + "/" + name if path != "/" else "/" + name
816865 # Translate to full key in the backing store and verify it's a data node or object root
817866 full_key = self ._translate_key_to_full (child_rel_path )
818- if super ().__contains__ (full_key ) or self ._object_info (full_key ) is not None :
867+ if (
868+ super ().__contains__ (full_key )
869+ or self ._object_info (full_key ) is not None
870+ or self ._probe_object_info (full_key ) is not None
871+ ):
819872 valid_leaf_nodes .append (name )
820873 leaf_nodes = valid_leaf_nodes
821874
@@ -865,7 +918,7 @@ def get_subtree(self, path: str) -> TreeStore:
865918 full_path = self ._translate_key_to_full (path )
866919
867920 # Object roots cannot be navigated as subtrees
868- if self ._object_info (full_path ) is not None :
921+ if ( self ._object_info (full_path ) or self . _probe_object_info ( full_path ) ) is not None :
869922 raise ValueError (
870923 f"'{ path } ' is an object root (e.g. CTable), not a TreeStore subtree. "
871924 f"Use ts['{ path } '] to access the object."
0 commit comments