Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tables/group.py: 19%
412 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-10 06:15 +0000
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-10 06:15 +0000
1"""Here is defined the Group class."""
3import os
4import weakref
5import warnings
7from .misc.proxydict import ProxyDict
8from . import hdf5extension
9from . import utilsextension
10from .registry import class_id_dict
11from .exceptions import (NodeError, NoSuchNodeError, NaturalNameWarning,
12 PerformanceWarning)
13from .filters import Filters
14from .registry import get_class_by_name
15from .path import check_name_validity, join_path, isvisiblename
16from .node import Node, NotLoggedMixin
17from .leaf import Leaf
18from .unimplemented import UnImplemented, Unknown
20from .link import Link, SoftLink, ExternalLink
23obversion = "1.0"
26class _ChildrenDict(ProxyDict):
27 def _get_value_from_container(self, container, key):
28 return container._f_get_child(key)
31class Group(hdf5extension.Group, Node):
32 """Basic PyTables grouping structure.
34 Instances of this class are grouping structures containing *child*
35 instances of zero or more groups or leaves, together with
36 supporting metadata. Each group has exactly one *parent* group.
38 Working with groups and leaves is similar in many ways to working
39 with directories and files, respectively, in a Unix filesystem.
40 As with Unix directories and files, objects in the object tree are
41 often described by giving their full (or absolute) path names.
42 This full path can be specified either as a string (like in
43 '/group1/group2') or as a complete object path written in *natural
44 naming* schema (like in file.root.group1.group2).
46 A collateral effect of the *natural naming* schema is that the
47 names of members in the Group class and its instances must be
48 carefully chosen to avoid colliding with existing children node
49 names. For this reason and to avoid polluting the children
50 namespace all members in a Group start with some reserved prefix,
51 like _f_ (for public methods), _g_ (for private ones), _v_ (for
52 instance variables) or _c_ (for class variables). Any attempt to
53 create a new child node whose name starts with one of these
54 prefixes will raise a ValueError exception.
56 Another effect of natural naming is that children named after
57 Python keywords or having names not valid as Python identifiers
58 (e.g. class, $a or 44) can not be accessed using the node.child
59 syntax. You will be forced to use node._f_get_child(child) to
60 access them (which is recommended for programmatic accesses).
62 You will also need to use _f_get_child() to access an existing
63 child node if you set a Python attribute in the Group with the
64 same name as that node (you will get a NaturalNameWarning when
65 doing this).
67 Parameters
68 ----------
69 parentnode
70 The parent :class:`Group` object.
71 name : str
72 The name of this node in its parent group.
73 title
74 The title for this group
75 new
76 If this group is new or has to be read from disk
77 filters : Filters
78 A Filters instance
81 .. versionchanged:: 3.0
82 *parentNode* renamed into *parentnode*
84 Notes
85 -----
86 The following documentation includes methods that are automatically
87 called when a Group instance is accessed in a special way.
89 For instance, this class defines the __setattr__, __getattr__,
90 __delattr__ and __dir__ methods, and they set, get and delete
91 *ordinary Python attributes* as normally intended. In addition to that,
92 __getattr__ allows getting *child nodes* by their name for the sake of
93 easy interaction on the command line, as long as there is no Python
94 attribute with the same name. Groups also allow the interactive
95 completion (when using readline) of the names of child nodes.
96 For instance::
98 # get a Python attribute
99 nchild = group._v_nchildren
101 # Add a Table child called 'table' under 'group'.
102 h5file.create_table(group, 'table', myDescription)
103 table = group.table # get the table child instance
104 group.table = 'foo' # set a Python attribute
106 # (PyTables warns you here about using the name of a child node.)
107 foo = group.table # get a Python attribute
108 del group.table # delete a Python attribute
109 table = group.table # get the table child instance again
111 Additionally, on interactive python sessions you may get autocompletions
112 of children named as *valid python identifiers* by pressing the `[Tab]`
113 key, or to use the dir() global function.
115 .. rubric:: Group attributes
117 The following instance variables are provided in addition to those
118 in Node (see :ref:`NodeClassDescr`):
120 .. attribute:: _v_children
122 Dictionary with all nodes hanging from this group.
124 .. attribute:: _v_groups
126 Dictionary with all groups hanging from this group.
128 .. attribute:: _v_hidden
130 Dictionary with all hidden nodes hanging from this group.
132 .. attribute:: _v_leaves
134 Dictionary with all leaves hanging from this group.
136 .. attribute:: _v_links
138 Dictionary with all links hanging from this group.
140 .. attribute:: _v_unknown
142 Dictionary with all unknown nodes hanging from this group.
144 """
146 # Class identifier.
147 _c_classid = 'GROUP'
149 # Children containers that should be loaded only in a lazy way.
150 # These are documented in the ``Group._g_add_children_names`` method.
151 _c_lazy_children_attrs = (
152 '__members__', '_v_children', '_v_groups', '_v_leaves',
153 '_v_links', '_v_unknown', '_v_hidden')
155 # `_v_nchildren` is a direct read-only shorthand
156 # for the number of *visible* children in a group.
157 def _g_getnchildren(self):
158 """The number of children hanging from this group."""
159 return len(self._v_children)
161 _v_nchildren = property(_g_getnchildren)
163 # `_v_filters` is a direct read-write shorthand for the ``FILTERS``
164 # attribute with the default `Filters` instance as a default value.
165 def _g_getfilters(self):
166 filters = getattr(self._v_attrs, 'FILTERS', None)
167 if filters is None:
168 filters = Filters()
169 return filters
171 def _g_setfilters(self, value):
172 if not isinstance(value, Filters):
173 raise TypeError(
174 f"value is not an instance of `Filters`: {value!r}")
175 self._v_attrs.FILTERS = value
177 def _g_delfilters(self):
178 del self._v_attrs.FILTERS
180 _v_filters = property(
181 _g_getfilters, _g_setfilters, _g_delfilters,
182 """Default filter properties for child nodes.
184 You can (and are encouraged to) use this property to get, set and
185 delete the FILTERS HDF5 attribute of the group, which stores a Filters
186 instance (see :ref:`FiltersClassDescr`). When the group has no such
187 attribute, a default Filters instance is used.
188 """)
190 def __init__(self, parentnode, name,
191 title="", new=False, filters=None,
192 _log=True):
194 # Remember to assign these values in the root group constructor
195 # if it does not use this one!
197 # First, set attributes belonging to group objects.
199 self._v_version = obversion
200 """The object version of this group."""
202 self._v_new = new
203 """Is this the first time the node has been created?"""
205 self._v_new_title = title
206 """New title for this node."""
208 self._v_new_filters = filters
209 """New default filter properties for child nodes."""
211 self._v_max_group_width = parentnode._v_file.params['MAX_GROUP_WIDTH']
212 """Maximum number of children on each group before warning the user.
214 .. versionchanged:: 3.0
215 The *_v_maxGroupWidth* attribute has been renamed into
216 *_v_max_group_width*.
218 """
220 # Finally, set up this object as a node.
221 super().__init__(parentnode, name, _log)
223 def _g_post_init_hook(self):
224 if self._v_new:
225 if self._v_file.params['PYTABLES_SYS_ATTRS']:
226 # Save some attributes for the new group on disk.
227 set_attr = self._v_attrs._g__setattr
228 # Set the title, class and version attributes.
229 set_attr('TITLE', self._v_new_title)
230 set_attr('CLASS', self._c_classid)
231 set_attr('VERSION', self._v_version)
233 # Set the default filter properties.
234 newfilters = self._v_new_filters
235 if newfilters is None:
236 # If no filters have been passed in the constructor,
237 # inherit them from the parent group, but only if they
238 # have been inherited or explicitly set.
239 newfilters = getattr(
240 self._v_parent._v_attrs, 'FILTERS', None)
241 if newfilters is not None:
242 set_attr('FILTERS', newfilters)
243 else:
244 # If the file has PyTables format, get the VERSION attr
245 if 'VERSION' in self._v_attrs._v_attrnamessys:
246 self._v_version = self._v_attrs.VERSION
247 else:
248 self._v_version = "0.0 (unknown)"
249 # We don't need to get more attributes from disk,
250 # since the most important ones are defined as properties.
252 def __del__(self):
253 if (self._v_isopen and
254 self._v_pathname in self._v_file._node_manager.registry and
255 '_v_children' in self.__dict__):
256 # The group is going to be killed. Rebuild weak references
257 # (that Python cancelled just before calling this method) so
258 # that they are still usable if the object is revived later.
259 selfref = weakref.ref(self)
260 self._v_children.containerref = selfref
261 self._v_groups.containerref = selfref
262 self._v_leaves.containerref = selfref
263 self._v_links.containerref = selfref
264 self._v_unknown.containerref = selfref
265 self._v_hidden.containerref = selfref
267 super().__del__()
269 def _g_get_child_group_class(self, childname):
270 """Get the class of a not-yet-loaded group child.
272 `childname` must be the name of a *group* child.
274 """
276 childCID = self._g_get_gchild_attr(childname, 'CLASS')
277 if childCID is not None and not isinstance(childCID, str):
278 childCID = childCID.decode('utf-8')
280 if childCID in class_id_dict:
281 return class_id_dict[childCID] # look up group class
282 else:
283 return Group # default group class
285 def _g_get_child_leaf_class(self, childname, warn=True):
286 """Get the class of a not-yet-loaded leaf child.
288 `childname` must be the name of a *leaf* child. If the child
289 belongs to an unknown kind of leaf, or if its kind can not be
290 guessed, `UnImplemented` will be returned and a warning will be
291 issued if `warn` is true.
293 """
295 if self._v_file.params['PYTABLES_SYS_ATTRS']:
296 childCID = self._g_get_lchild_attr(childname, 'CLASS')
297 if childCID is not None and not isinstance(childCID, str):
298 childCID = childCID.decode('utf-8')
299 else:
300 childCID = None
302 if childCID in class_id_dict:
303 return class_id_dict[childCID] # look up leaf class
304 else:
305 # Unknown or no ``CLASS`` attribute, try a guess.
306 childCID2 = utilsextension.which_class(self._v_objectid, childname)
307 if childCID2 == 'UNSUPPORTED':
308 if warn:
309 if childCID is None:
310 warnings.warn(
311 "leaf ``%s`` is of an unsupported type; "
312 "it will become an ``UnImplemented`` node"
313 % self._g_join(childname))
314 else:
315 warnings.warn(
316 ("leaf ``%s`` has an unknown class ID ``%s``; "
317 "it will become an ``UnImplemented`` node")
318 % (self._g_join(childname), childCID))
319 return UnImplemented
320 assert childCID2 in class_id_dict
321 return class_id_dict[childCID2] # look up leaf class
323 def _g_add_children_names(self):
324 """Add children names to this group taking into account their
325 visibility and kind."""
327 mydict = self.__dict__
329 # The names of the lazy attributes
330 mydict['__members__'] = members = []
331 """The names of visible children nodes for readline-style completion.
332 """
333 mydict['_v_children'] = children = _ChildrenDict(self)
334 """The number of children hanging from this group."""
335 mydict['_v_groups'] = groups = _ChildrenDict(self)
336 """Dictionary with all groups hanging from this group."""
337 mydict['_v_leaves'] = leaves = _ChildrenDict(self)
338 """Dictionary with all leaves hanging from this group."""
339 mydict['_v_links'] = links = _ChildrenDict(self)
340 """Dictionary with all links hanging from this group."""
341 mydict['_v_unknown'] = unknown = _ChildrenDict(self)
342 """Dictionary with all unknown nodes hanging from this group."""
343 mydict['_v_hidden'] = hidden = _ChildrenDict(self)
344 """Dictionary with all hidden nodes hanging from this group."""
346 # Get the names of *all* child groups and leaves.
347 (group_names, leaf_names, link_names, unknown_names) = \
348 self._g_list_group(self._v_parent)
350 # Separate groups into visible groups and hidden nodes,
351 # and leaves into visible leaves and hidden nodes.
352 for (childnames, childdict) in ((group_names, groups),
353 (leaf_names, leaves),
354 (link_names, links),
355 (unknown_names, unknown)):
357 for childname in childnames:
358 # See whether the name implies that the node is hidden.
359 # (Assigned values are entirely irrelevant.)
360 if isvisiblename(childname):
361 # Visible node.
362 members.insert(0, childname)
363 children[childname] = None
364 childdict[childname] = None
365 else:
366 # Hidden node.
367 hidden[childname] = None
369 def _g_check_has_child(self, name):
370 """Check whether 'name' is a children of 'self' and return its type."""
372 # Get the HDF5 name matching the PyTables name.
373 node_type = self._g_get_objinfo(name)
374 if node_type == "NoSuchNode":
375 raise NoSuchNodeError(
376 "group ``%s`` does not have a child named ``%s``"
377 % (self._v_pathname, name))
378 return node_type
380 def __iter__(self):
381 """Iterate over the child nodes hanging directly from the group.
383 This iterator is *not* recursive.
385 Examples
386 --------
388 ::
390 # Non-recursively list all the nodes hanging from '/detector'
391 print("Nodes in '/detector' group:")
392 for node in h5file.root.detector:
393 print(node)
395 """
397 return self._f_iter_nodes()
399 def __contains__(self, name):
400 """Is there a child with that `name`?
402 Returns a true value if the group has a child node (visible or
403 hidden) with the given `name` (a string), false otherwise.
405 """
407 self._g_check_open()
408 try:
409 self._g_check_has_child(name)
410 except NoSuchNodeError:
411 return False
412 return True
414 def __getitem__(self, childname):
415 """Return the (visible or hidden) child with that `name` ( a string).
417 Raise IndexError if child not exist.
418 """
419 try:
420 return self._f_get_child(childname)
421 except NoSuchNodeError:
422 raise IndexError(childname)
424 def _f_walknodes(self, classname=None):
425 """Iterate over descendant nodes.
427 This method recursively walks *self* top to bottom (preorder),
428 iterating over child groups in alphanumerical order, and yielding
429 nodes. If classname is supplied, only instances of the named class are
430 yielded.
432 If *classname* is Group, it behaves like :meth:`Group._f_walk_groups`,
433 yielding only groups. If you don't want a recursive behavior,
434 use :meth:`Group._f_iter_nodes` instead.
436 Examples
437 --------
439 ::
441 # Recursively print all the arrays hanging from '/'
442 print("Arrays in the object tree '/':")
443 for array in h5file.root._f_walknodes('Array', recursive=True):
444 print(array)
446 """
448 self._g_check_open()
450 # For compatibility with old default arguments.
451 if classname == '':
452 classname = None
454 if classname == "Group":
455 # Recursive algorithm
456 yield from self._f_walk_groups()
457 else:
458 for group in self._f_walk_groups():
459 yield from group._f_iter_nodes(classname)
461 def _g_join(self, name):
462 """Helper method to correctly concatenate a name child object with the
463 pathname of this group."""
465 if name == "/":
466 # This case can happen when doing copies
467 return self._v_pathname
468 return join_path(self._v_pathname, name)
470 def _g_width_warning(self):
471 """Issue a :exc:`PerformanceWarning` on too many children."""
473 warnings.warn("""\
474group ``%s`` is exceeding the recommended maximum number of children (%d); \
475be ready to see PyTables asking for *lots* of memory and possibly slow I/O."""
476 % (self._v_pathname, self._v_max_group_width),
477 PerformanceWarning)
479 def _g_refnode(self, childnode, childname, validate=True):
480 """Insert references to a `childnode` via a `childname`.
482 Checks that the `childname` is valid and does not exist, then
483 creates references to the given `childnode` by that `childname`.
484 The validation of the name can be omitted by setting `validate`
485 to a false value (this may be useful for adding already existing
486 nodes to the tree).
488 """
490 # Check for name validity.
491 if validate:
492 check_name_validity(childname)
493 childnode._g_check_name(childname)
495 # Check if there is already a child with the same name.
496 # This can be triggered because of the user
497 # (via node construction or renaming/movement).
498 # Links are not checked here because they are copied and referenced
499 # using ``File.get_node`` so they already exist in `self`.
500 if (not isinstance(childnode, Link)) and childname in self:
501 raise NodeError(
502 "group ``%s`` already has a child node named ``%s``"
503 % (self._v_pathname, childname))
505 # Show a warning if there is an object attribute with that name.
506 if childname in self.__dict__:
507 warnings.warn(
508 "group ``%s`` already has an attribute named ``%s``; "
509 "you will not be able to use natural naming "
510 "to access the child node"
511 % (self._v_pathname, childname), NaturalNameWarning)
513 # Check group width limits.
514 if (len(self._v_children) + len(self._v_hidden) >=
515 self._v_max_group_width):
516 self._g_width_warning()
518 # Update members information.
519 # Insert references to the new child.
520 # (Assigned values are entirely irrelevant.)
521 if isvisiblename(childname):
522 # Visible node.
523 self.__members__.insert(0, childname) # enable completion
524 self._v_children[childname] = None # insert node
525 if isinstance(childnode, Unknown):
526 self._v_unknown[childname] = None
527 elif isinstance(childnode, Link):
528 self._v_links[childname] = None
529 elif isinstance(childnode, Leaf):
530 self._v_leaves[childname] = None
531 elif isinstance(childnode, Group):
532 self._v_groups[childname] = None
533 else:
534 # Hidden node.
535 self._v_hidden[childname] = None # insert node
537 def _g_unrefnode(self, childname):
538 """Remove references to a node.
540 Removes all references to the named node.
542 """
544 # This can *not* be triggered because of the user.
545 assert childname in self, \
546 ("group ``%s`` does not have a child node named ``%s``"
547 % (self._v_pathname, childname))
549 # Update members information, if needed
550 if '_v_children' in self.__dict__:
551 if childname in self._v_children:
552 # Visible node.
553 members = self.__members__
554 member_index = members.index(childname)
555 del members[member_index] # disables completion
557 del self._v_children[childname] # remove node
558 self._v_unknown.pop(childname, None)
559 self._v_links.pop(childname, None)
560 self._v_leaves.pop(childname, None)
561 self._v_groups.pop(childname, None)
562 else:
563 # Hidden node.
564 del self._v_hidden[childname] # remove node
566 def _g_move(self, newparent, newname):
567 # Move the node to the new location.
568 oldpath = self._v_pathname
569 super()._g_move(newparent, newname)
570 newpath = self._v_pathname
572 # Update location information in children. This node shouldn't
573 # be affected since it has already been relocated.
574 self._v_file._update_node_locations(oldpath, newpath)
576 def _g_copy(self, newparent, newname, recursive, _log=True, **kwargs):
577 # Compute default arguments.
578 title = kwargs.get('title', self._v_title)
579 filters = kwargs.get('filters', None)
580 stats = kwargs.get('stats', None)
582 # Fix arguments with explicit None values for backwards compatibility.
583 if title is None:
584 title = self._v_title
585 # If no filters have been passed to the call, copy them from the
586 # source group, but only if inherited or explicitly set.
587 if filters is None:
588 filters = getattr(self._v_attrs, 'FILTERS', None)
590 # Create a copy of the object.
591 new_node = Group(newparent, newname,
592 title, new=True, filters=filters, _log=_log)
594 # Copy user attributes if needed.
595 if kwargs.get('copyuserattrs', True):
596 self._v_attrs._g_copy(new_node._v_attrs, copyclass=True)
598 # Update statistics if needed.
599 if stats is not None:
600 stats['groups'] += 1
602 if recursive:
603 # Copy child nodes if a recursive copy was requested.
604 # Some arguments should *not* be passed to children copy ops.
605 kwargs = kwargs.copy()
606 kwargs.pop('title', None)
607 self._g_copy_children(new_node, **kwargs)
609 return new_node
611 def _g_copy_children(self, newparent, **kwargs):
612 """Copy child nodes.
614 Copies all nodes descending from this one into the specified
615 `newparent`. If the new parent has a child node with the same
616 name as one of the nodes in this group, the copy fails with a
617 `NodeError`, maybe resulting in a partial copy. Nothing is
618 logged.
620 """
622 # Recursive version of children copy.
623 # for srcchild in self._v_children.itervalues():
624 # srcchild._g_copy_as_child(newparent, **kwargs)
626 # Non-recursive version of children copy.
627 use_hardlinks = kwargs.get('use_hardlinks', False)
628 if use_hardlinks:
629 address_map = kwargs.setdefault('address_map', {})
631 parentstack = [(self, newparent)] # [(source, destination), ...]
632 while parentstack:
633 (srcparent, dstparent) = parentstack.pop()
635 if use_hardlinks:
636 for srcchild in srcparent._v_children.values():
637 addr, rc = srcchild._get_obj_info()
638 if rc > 1 and addr in address_map:
639 where, name = address_map[addr][0]
640 localsrc = os.path.join(where, name)
641 dstparent._v_file.create_hard_link(dstparent,
642 srcchild.name,
643 localsrc)
644 address_map[addr].append(
645 (dstparent._v_pathname, srcchild.name)
646 )
648 # Update statistics if needed.
649 stats = kwargs.pop('stats', None)
650 if stats is not None:
651 stats['hardlinks'] += 1
652 else:
653 dstchild = srcchild._g_copy_as_child(dstparent,
654 **kwargs)
655 if isinstance(srcchild, Group):
656 parentstack.append((srcchild, dstchild))
658 if rc > 1:
659 address_map[addr] = [
660 (dstparent._v_pathname, srcchild.name)
661 ]
662 else:
663 for srcchild in srcparent._v_children.values():
664 dstchild = srcchild._g_copy_as_child(dstparent, **kwargs)
665 if isinstance(srcchild, Group):
666 parentstack.append((srcchild, dstchild))
668 def _f_get_child(self, childname):
669 """Get the child called childname of this group.
671 If the child exists (be it visible or not), it is returned. Else, a
672 NoSuchNodeError is raised.
674 Using this method is recommended over getattr() when doing programmatic
675 accesses to children if childname is unknown beforehand or when its
676 name is not a valid Python identifier.
678 """
680 self._g_check_open()
682 self._g_check_has_child(childname)
684 childpath = join_path(self._v_pathname, childname)
685 return self._v_file._get_node(childpath)
687 def _f_list_nodes(self, classname=None):
688 """Return a *list* with children nodes.
690 This is a list-returning version of :meth:`Group._f_iter_nodes()`.
692 """
694 return list(self._f_iter_nodes(classname))
696 def _f_iter_nodes(self, classname=None):
697 """Iterate over children nodes.
699 Child nodes are yielded alphanumerically sorted by node name. If the
700 name of a class derived from Node (see :ref:`NodeClassDescr`) is
701 supplied in the classname parameter, only instances of that class (or
702 subclasses of it) will be returned.
704 This is an iterator version of :meth:`Group._f_list_nodes`.
706 """
708 self._g_check_open()
710 if not classname:
711 # Returns all the children alphanumerically sorted
712 for name in sorted(self._v_children):
713 yield self._v_children[name]
714 elif classname == 'Group':
715 # Returns all the groups alphanumerically sorted
716 for name in sorted(self._v_groups):
717 yield self._v_groups[name]
718 elif classname == 'Leaf':
719 # Returns all the leaves alphanumerically sorted
720 for name in sorted(self._v_leaves):
721 yield self._v_leaves[name]
722 elif classname == 'Link':
723 # Returns all the links alphanumerically sorted
724 for name in sorted(self._v_links):
725 yield self._v_links[name]
726 elif classname == 'IndexArray':
727 raise TypeError(
728 "listing ``IndexArray`` nodes is not allowed")
729 else:
730 class_ = get_class_by_name(classname)
731 for childname, childnode in sorted(self._v_children.items()):
732 if isinstance(childnode, class_):
733 yield childnode
735 def _f_walk_groups(self):
736 """Recursively iterate over descendent groups (not leaves).
738 This method starts by yielding *self*, and then it goes on to
739 recursively iterate over all child groups in alphanumerical order, top
740 to bottom (preorder), following the same procedure.
742 """
744 self._g_check_open()
746 stack = [self]
747 yield self
748 # Iterate over the descendants
749 while stack:
750 objgroup = stack.pop()
751 groupnames = sorted(objgroup._v_groups)
752 # Sort the groups before delivering. This uses the groups names
753 # for groups in tree (in order to sort() can classify them).
754 for groupname in groupnames:
755 # TODO: check recursion
756 stack.append(objgroup._v_groups[groupname])
757 yield objgroup._v_groups[groupname]
759 def __delattr__(self, name):
760 """Delete a Python attribute called name.
762 This method only provides a extra warning in case the user
763 tries to delete a children node using __delattr__.
765 To remove a children node from this group use
766 :meth:`File.remove_node` or :meth:`Node._f_remove`. To delete
767 a PyTables node attribute use :meth:`File.del_node_attr`,
768 :meth:`Node._f_delattr` or :attr:`Node._v_attrs``.
770 If there is an attribute and a child node with the same name,
771 the child node will be made accessible again via natural naming.
773 """
775 try:
776 super().__delattr__(name) # nothing particular
777 except AttributeError as ae:
778 hint = " (use ``node._f_remove()`` if you want to remove a node)"
779 raise ae.__class__(str(ae) + hint)
781 def __dir__(self):
782 """Autocomplete only children named as valid python identifiers.
784 Only PY3 supports this special method.
785 """
786 subnods = [c for c in self._v_children if c.isidentifier()]
787 return super().__dir__() + subnods
789 def __getattr__(self, name):
790 """Get a Python attribute or child node called name.
791 If the node has a child node called name it is returned,
792 else an AttributeError is raised.
793 """
795 if name in self._c_lazy_children_attrs:
796 self._g_add_children_names()
797 return self.__dict__[name]
798 return self._f_get_child(name)
800 def __setattr__(self, name, value):
801 """Set a Python attribute called name with the given value.
803 This method stores an *ordinary Python attribute* in the object. It
804 does *not* store new children nodes under this group; for that, use the
805 File.create*() methods (see the File class
806 in :ref:`FileClassDescr`). It does *neither* store a PyTables node
807 attribute; for that,
808 use :meth:`File.set_node_attr`, :meth`:Node._f_setattr`
809 or :attr:`Node._v_attrs`.
811 If there is already a child node with the same name, a
812 NaturalNameWarning will be issued and the child node will not be
813 accessible via natural naming nor getattr(). It will still be available
814 via :meth:`File.get_node`, :meth:`Group._f_get_child` and children
815 dictionaries in the group (if visible).
817 """
819 # Show a warning if there is an child node with that name.
820 #
821 # ..note::
822 #
823 # Using ``if name in self:`` is not right since that would
824 # require ``_v_children`` and ``_v_hidden`` to be already set
825 # when the very first attribute assignments are made.
826 # Moreover, this warning is only concerned about clashes with
827 # names used in natural naming, i.e. those in ``__members__``.
828 #
829 # ..note::
830 #
831 # The check ``'__members__' in myDict`` allows attribute
832 # assignment to happen before calling `Group.__init__()`, by
833 # avoiding to look into the still not assigned ``__members__``
834 # attribute. This allows subclasses to set up some attributes
835 # and then call the constructor of the superclass. If the
836 # check above is disabled, that results in Python entering an
837 # endless loop on exit!
839 mydict = self.__dict__
840 if '__members__' in mydict and name in self.__members__:
841 warnings.warn(
842 "group ``%s`` already has a child node named ``%s``; "
843 "you will not be able to use natural naming "
844 "to access the child node"
845 % (self._v_pathname, name), NaturalNameWarning)
847 super().__setattr__(name, value)
849 def _f_flush(self):
850 """Flush this Group."""
852 self._g_check_open()
853 self._g_flush_group()
855 def _g_close_descendents(self):
856 """Close all the *loaded* descendent nodes of this group."""
858 node_manager = self._v_file._node_manager
859 node_manager.close_subtree(self._v_pathname)
861 def _g_close(self):
862 """Close this (open) group."""
864 if self._v_isopen:
865 # hdf5extension operations:
866 # Close HDF5 group.
867 self._g_close_group()
869 # Close myself as a node.
870 super()._f_close()
872 def _f_close(self):
873 """Close this group and all its descendents.
875 This method has the behavior described in :meth:`Node._f_close`.
876 It should be noted that this operation closes all the nodes
877 descending from this group.
879 You should not need to close nodes manually because they are
880 automatically opened/closed when they are loaded/evicted from
881 the integrated LRU cache.
883 """
885 # If the group is already closed, return immediately
886 if not self._v_isopen:
887 return
889 # First, close all the descendents of this group, unless a) the
890 # group is being deleted (evicted from LRU cache) or b) the node
891 # is being closed during an aborted creation, in which cases
892 # this is not an explicit close issued by the user.
893 if not (self._v__deleting or self._v_objectid is None):
894 self._g_close_descendents()
896 # When all the descendents have been closed, close this group.
897 # This is done at the end because some nodes may still need to
898 # be loaded during the closing process; thus this node must be
899 # open until the very end.
900 self._g_close()
902 def _g_remove(self, recursive=False, force=False):
903 """Remove (recursively if needed) the Group.
905 This version correctly handles both visible and hidden nodes.
907 """
909 if self._v_nchildren > 0:
910 if not (recursive or force):
911 raise NodeError("group ``%s`` has child nodes; "
912 "please set `recursive` or `force` to true "
913 "to remove it"
914 % (self._v_pathname,))
916 # First close all the descendents hanging from this group,
917 # so that it is not possible to use a node that no longer exists.
918 self._g_close_descendents()
920 # Remove the node itself from the hierarchy.
921 super()._g_remove(recursive, force)
923 def _f_copy(self, newparent=None, newname=None,
924 overwrite=False, recursive=False, createparents=False,
925 **kwargs):
926 """Copy this node and return the new one.
928 This method has the behavior described in :meth:`Node._f_copy`.
929 In addition, it recognizes the following keyword arguments:
931 Parameters
932 ----------
933 title
934 The new title for the destination. If omitted or None, the
935 original title is used. This only applies to the topmost
936 node in recursive copies.
937 filters : Filters
938 Specifying this parameter overrides the original filter
939 properties in the source node. If specified, it must be an
940 instance of the Filters class (see :ref:`FiltersClassDescr`).
941 The default is to copy the filter properties from the source
942 node.
943 copyuserattrs
944 You can prevent the user attributes from being copied by setting
945 thisparameter to False. The default is to copy them.
946 stats
947 This argument may be used to collect statistics on the copy
948 process. When used, it should be a dictionary with keys 'groups',
949 'leaves', 'links' and 'bytes' having a numeric value. Their values
950 willbe incremented to reflect the number of groups, leaves and
951 bytes, respectively, that have been copied during the operation.
953 """
955 return super()._f_copy(
956 newparent, newname,
957 overwrite, recursive, createparents, **kwargs)
959 def _f_copy_children(self, dstgroup, overwrite=False, recursive=False,
960 createparents=False, **kwargs):
961 """Copy the children of this group into another group.
963 Children hanging directly from this group are copied into dstgroup,
964 which can be a Group (see :ref:`GroupClassDescr`) object or its
965 pathname in string form. If createparents is true, the needed groups
966 for the given destination group path to exist will be created.
968 The operation will fail with a NodeError if there is a child node
969 in the destination group with the same name as one of the copied
970 children from this one, unless overwrite is true; in this case,
971 the former child node is recursively removed before copying the
972 later.
974 By default, nodes descending from children groups of this node
975 are not copied. If the recursive argument is true, all descendant
976 nodes of this node are recursively copied.
978 Additional keyword arguments may be passed to customize the
979 copying process. For instance, title and filters may be changed,
980 user attributes may be or may not be copied, data may be sub-sampled,
981 stats may be collected, etc. Arguments unknown to nodes are simply
982 ignored. Check the documentation for copying operations of nodes to
983 see which options they support.
985 """
987 self._g_check_open()
989 # `dstgroup` is used instead of its path to avoid accepting
990 # `Node` objects when `createparents` is true. Also, note that
991 # there is no risk of creating parent nodes and failing later
992 # because of destination nodes already existing.
993 dstparent = self._v_file._get_or_create_path(dstgroup, createparents)
994 self._g_check_group(dstparent) # Is it a group?
996 if not overwrite:
997 # Abort as early as possible when destination nodes exist
998 # and overwriting is not enabled.
999 for childname in self._v_children:
1000 if childname in dstparent:
1001 raise NodeError(
1002 "destination group ``%s`` already has "
1003 "a node named ``%s``; "
1004 "you may want to use the ``overwrite`` argument"
1005 % (dstparent._v_pathname, childname))
1007 use_hardlinks = kwargs.get('use_hardlinks', False)
1008 if use_hardlinks:
1009 address_map = kwargs.setdefault('address_map', {})
1011 for child in self._v_children.values():
1012 addr, rc = child._get_obj_info()
1013 if rc > 1 and addr in address_map:
1014 where, name = address_map[addr][0]
1015 localsrc = os.path.join(where, name)
1016 dstparent._v_file.create_hard_link(dstparent, child.name,
1017 localsrc)
1018 address_map[addr].append(
1019 (dstparent._v_pathname, child.name)
1020 )
1022 # Update statistics if needed.
1023 stats = kwargs.pop('stats', None)
1024 if stats is not None:
1025 stats['hardlinks'] += 1
1026 else:
1027 child._f_copy(dstparent, None, overwrite, recursive,
1028 **kwargs)
1029 if rc > 1:
1030 address_map[addr] = [
1031 (dstparent._v_pathname, child.name)
1032 ]
1033 else:
1034 for child in self._v_children.values():
1035 child._f_copy(dstparent, None, overwrite, recursive, **kwargs)
1037 def __str__(self):
1038 """Return a short string representation of the group.
1040 Examples
1041 --------
1043 ::
1045 >>> import tables
1046 >>> f = tables.open_file('tables/tests/Tables_lzo2.h5')
1047 >>> print(f.root.group0)
1048 /group0 (Group) ''
1049 >>> f.close()
1051 """
1053 return (f"{self._v_pathname} ({self.__class__.__name__}) "
1054 f"{self._v_title!r}")
1056 def __repr__(self):
1057 """Return a detailed string representation of the group.
1059 Examples
1060 --------
1062 ::
1064 >>> import tables
1065 >>> f = tables.open_file('tables/tests/Tables_lzo2.h5')
1066 >>> f.root.group0
1067 /group0 (Group) ''
1068 children := ['group1' (Group), 'tuple1' (Table)]
1069 >>> f.close()
1071 """
1073 rep = [
1074 f'{childname!r} ({child.__class__.__name__})'
1075 for (childname, child) in self._v_children.items()
1076 ]
1077 return f'{self!s}\n children := [{", ".join(rep)}]'
1080# Special definition for group root
1081class RootGroup(Group):
1083 def __init__(self, ptfile, name, title, new, filters):
1084 mydict = self.__dict__
1086 # Set group attributes.
1087 self._v_version = obversion
1088 self._v_new = new
1089 if new:
1090 self._v_new_title = title
1091 self._v_new_filters = filters
1092 else:
1093 self._v_new_title = None
1094 self._v_new_filters = None
1096 # Set node attributes.
1097 self._v_file = ptfile
1098 self._v_isopen = True # root is always open
1099 self._v_pathname = '/'
1100 self._v_name = '/'
1101 self._v_depth = 0
1102 self._v_max_group_width = ptfile.params['MAX_GROUP_WIDTH']
1103 self._v__deleting = False
1104 self._v_objectid = None # later
1106 # Only the root node has the file as a parent.
1107 # Bypass __setattr__ to avoid the ``Node._v_parent`` property.
1108 mydict['_v_parent'] = ptfile
1109 ptfile._node_manager.register_node(self, '/')
1111 # hdf5extension operations (do before setting an AttributeSet):
1112 # Update node attributes.
1113 self._g_new(ptfile, name, init=True)
1114 # Open the node and get its object ID.
1115 self._v_objectid = self._g_open()
1117 # Set disk attributes and read children names.
1118 #
1119 # This *must* be postponed because this method needs the root node
1120 # to be created and bound to ``File.root``.
1121 # This is an exception to the rule, handled by ``File.__init()__``.
1122 #
1123 # self._g_post_init_hook()
1125 def _g_load_child(self, childname):
1126 """Load a child node from disk.
1128 The child node `childname` is loaded from disk and an adequate
1129 `Node` object is created and returned. If there is no such
1130 child, a `NoSuchNodeError` is raised.
1132 """
1134 if self._v_file.root_uep != "/":
1135 childname = join_path(self._v_file.root_uep, childname)
1136 # Is the node a group or a leaf?
1137 node_type = self._g_check_has_child(childname)
1139 # Nodes that HDF5 report as H5G_UNKNOWN
1140 if node_type == 'Unknown':
1141 return Unknown(self, childname)
1143 # Guess the PyTables class suited to the node,
1144 # build a PyTables node and return it.
1145 if node_type == "Group":
1146 if self._v_file.params['PYTABLES_SYS_ATTRS']:
1147 ChildClass = self._g_get_child_group_class(childname)
1148 else:
1149 # Default is a Group class
1150 ChildClass = Group
1151 return ChildClass(self, childname, new=False)
1152 elif node_type == "Leaf":
1153 ChildClass = self._g_get_child_leaf_class(childname, warn=True)
1154 # Building a leaf may still fail because of unsupported types
1155 # and other causes.
1156 # return ChildClass(self, childname) # uncomment for debugging
1157 try:
1158 return ChildClass(self, childname)
1159 except Exception as exc: # XXX
1160 warnings.warn(
1161 "problems loading leaf ``%s``::\n\n"
1162 " %s\n\n"
1163 "The leaf will become an ``UnImplemented`` node."
1164 % (self._g_join(childname), exc))
1165 # If not, associate an UnImplemented object to it
1166 return UnImplemented(self, childname)
1167 elif node_type == "SoftLink":
1168 return SoftLink(self, childname)
1169 elif node_type == "ExternalLink":
1170 return ExternalLink(self, childname)
1171 else:
1172 return UnImplemented(self, childname)
1174 def _f_rename(self, newname):
1175 raise NodeError("the root node can not be renamed")
1177 def _f_move(self, newparent=None, newname=None, createparents=False):
1178 raise NodeError("the root node can not be moved")
1180 def _f_remove(self, recursive=False):
1181 raise NodeError("the root node can not be removed")
1184class TransactionGroupG(NotLoggedMixin, Group):
1185 _c_classid = 'TRANSGROUP'
1187 def _g_width_warning(self):
1188 warnings.warn("""\
1189the number of transactions is exceeding the recommended maximum (%d);\
1190be ready to see PyTables asking for *lots* of memory and possibly slow I/O"""
1191 % (self._v_max_group_width,), PerformanceWarning)
1194class TransactionG(NotLoggedMixin, Group):
1195 _c_classid = 'TRANSG'
1197 def _g_width_warning(self):
1198 warnings.warn("""\
1199transaction ``%s`` is exceeding the recommended maximum number of marks (%d);\
1200be ready to see PyTables asking for *lots* of memory and possibly slow I/O"""
1201 % (self._v_pathname, self._v_max_group_width),
1202 PerformanceWarning)
1205class MarkG(NotLoggedMixin, Group):
1206 # Class identifier.
1207 _c_classid = 'MARKG'
1209 import re
1210 _c_shadow_name_re = re.compile(r'^a[0-9]+$')
1212 def _g_width_warning(self):
1213 warnings.warn("""\
1214mark ``%s`` is exceeding the recommended maximum action storage (%d nodes);\
1215be ready to see PyTables asking for *lots* of memory and possibly slow I/O"""
1216 % (self._v_pathname, self._v_max_group_width),
1217 PerformanceWarning)
1219 def _g_reset(self):
1220 """Empty action storage (nodes and attributes).
1222 This method empties all action storage kept in this node: nodes
1223 and attributes.
1225 """
1227 # Remove action storage nodes.
1228 for child in list(self._v_children.values()):
1229 child._g_remove(True, True)
1231 # Remove action storage attributes.
1232 attrs = self._v_attrs
1233 shname = self._c_shadow_name_re
1234 for attrname in attrs._v_attrnamesuser[:]:
1235 if shname.match(attrname):
1236 attrs._g__delattr(attrname)