Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tables/link.py: 31%
116 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"""Create links in the HDF5 file.
3This module implements containers for soft and external links. Hard
4links doesn't need a container as such as they are the same as regular
5nodes (groups or leaves).
7Classes:
9 SoftLink
10 ExternalLink
12Functions:
14Misc variables:
16"""
18from pathlib import Path
20import tables as tb
22from . import linkextension
23from .node import Node
24from .utils import lazyattr
25from .attributeset import AttributeSet
28def _g_get_link_class(parent_id, name):
29 """Guess the link class."""
31 return linkextension._get_link_class(parent_id, name)
34class Link(Node):
35 """Abstract base class for all PyTables links.
37 A link is a node that refers to another node. The Link class inherits from
38 Node class and the links that inherits from Link are SoftLink and
39 ExternalLink. There is not a HardLink subclass because hard links behave
40 like a regular Group or Leaf. Contrarily to other nodes, links cannot have
41 HDF5 attributes. This is an HDF5 library limitation that might be solved
42 in future releases.
44 See :ref:`LinksTutorial` for a small tutorial on how to work with links.
46 .. rubric:: Link attributes
48 .. attribute:: target
50 The path string to the pointed node.
52 """
54 # Properties
55 @lazyattr
56 def _v_attrs(self):
57 """
58 A *NoAttrs* instance replacing the typical *AttributeSet* instance of
59 other node objects. The purpose of *NoAttrs* is to make clear that
60 HDF5 attributes are not supported in link nodes.
61 """
62 class NoAttrs(AttributeSet):
63 def __getattr__(self, name):
64 raise KeyError("you cannot get attributes from this "
65 "`%s` instance" % self.__class__.__name__)
67 def __setattr__(self, name, value):
68 raise KeyError("you cannot set attributes to this "
69 "`%s` instance" % self.__class__.__name__)
71 def _g_close(self):
72 pass
73 return NoAttrs(self)
75 def __init__(self, parentnode, name, target=None, _log=False):
76 self._v_new = target is not None
77 self.target = target
78 """The path string to the pointed node."""
80 super().__init__(parentnode, name, _log)
82 # Public and tailored versions for copy, move, rename and remove methods
83 def copy(self, newparent=None, newname=None,
84 overwrite=False, createparents=False):
85 """Copy this link and return the new one.
87 See :meth:`Node._f_copy` for a complete explanation of the arguments.
88 Please note that there is no recursive flag since links do not have
89 child nodes.
91 """
93 newnode = self._f_copy(newparent=newparent, newname=newname,
94 overwrite=overwrite,
95 createparents=createparents)
96 # Insert references to a `newnode` via `newname`
97 newnode._v_parent._g_refnode(newnode, newname, True)
98 return newnode
100 def move(self, newparent=None, newname=None, overwrite=False):
101 """Move or rename this link.
103 See :meth:`Node._f_move` for a complete explanation of the arguments.
105 """
107 return self._f_move(newparent=newparent, newname=newname,
108 overwrite=overwrite)
110 def remove(self):
111 """Remove this link from the hierarchy."""
113 return self._f_remove()
115 def rename(self, newname=None, overwrite=False):
116 """Rename this link in place.
118 See :meth:`Node._f_rename` for a complete explanation of the arguments.
120 """
122 return self._f_rename(newname=newname, overwrite=overwrite)
124 def __repr__(self):
125 return str(self)
128class SoftLink(linkextension.SoftLink, Link):
129 """Represents a soft link (aka symbolic link).
131 A soft link is a reference to another node in the *same* file hierarchy.
132 Provided that the target node exists, its attributes and methods can be
133 accessed directly from the softlink using the normal `.` syntax.
135 Softlinks also have the following public methods/attributes:
137 * `target`
138 * `dereference()`
139 * `copy()`
140 * `move()`
141 * `remove()`
142 * `rename()`
143 * `is_dangling()`
145 Note that these will override any correspondingly named methods/attributes
146 of the target node.
148 For backwards compatibility, it is also possible to obtain the target node
149 via the `__call__()` special method (this action is called *dereferencing*;
150 see below)
152 Examples
153 --------
155 ::
157 >>> import numpy as np
158 >>> f = tb.open_file('/tmp/test_softlink.h5', 'w')
159 >>> a = f.create_array('/', 'A', np.arange(10))
160 >>> link_a = f.create_soft_link('/', 'link_A', target='/A')
162 # transparent read/write access to a softlinked node
163 >>> link_a[0] = -1
164 >>> link_a[:], link_a.dtype
165 (array([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9]), dtype('int64'))
167 # dereferencing a softlink using the __call__() method
168 >>> link_a() is a
169 True
171 # SoftLink.remove() overrides Array.remove()
172 >>> link_a.remove()
173 >>> print(link_a)
174 <closed tables.link.SoftLink at ...>
175 >>> a[:], a.dtype
176 (array([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9]), dtype('int64'))
177 >>> f.close()
180 """
182 # Class identifier.
183 _c_classid = 'SOFTLINK'
185 # attributes with these names/prefixes are treated as attributes of the
186 # SoftLink rather than the target node
187 _link_attrnames = ('target', 'dereference', 'is_dangling', 'copy', 'move',
188 'remove', 'rename', '__init__', '__str__', '__repr__',
189 '__unicode__', '__class__', '__dict__')
190 _link_attrprefixes = ('_f_', '_c_', '_g_', '_v_')
192 def __call__(self):
193 """Dereference `self.target` and return the object.
195 Examples
196 --------
198 ::
200 >>> f = tb.open_file('tables/tests/slink.h5')
201 >>> f.root.arr2
202 /arr2 (SoftLink) -> /arr
203 >>> print(f.root.arr2())
204 /arr (Array(2,)) ''
205 >>> f.close()
207 """
208 return self.dereference()
210 def dereference(self):
212 if self._v_isopen:
213 target = self.target
214 # Check for relative pathnames
215 if not self.target.startswith('/'):
216 target = self._v_parent._g_join(self.target)
217 return self._v_file._get_node(target)
218 else:
219 return None
221 def __getattribute__(self, attrname):
223 # get attribute of the SoftLink itself
224 if (attrname in SoftLink._link_attrnames or
225 attrname[:3] in SoftLink._link_attrprefixes):
226 return object.__getattribute__(self, attrname)
228 # get attribute of the target node
229 elif not self._v_isopen:
230 raise tb.ClosedNodeError('the node object is closed')
231 elif self.is_dangling():
232 return None
233 else:
234 target_node = self.dereference()
235 try:
236 # __getattribute__() fails to get children of Groups
237 return target_node.__getattribute__(attrname)
238 except AttributeError:
239 # some node classes (e.g. Array) don't implement __getattr__()
240 return target_node.__getattr__(attrname)
242 def __setattr__(self, attrname, value):
244 # set attribute of the SoftLink itself
245 if (attrname in SoftLink._link_attrnames or
246 attrname[:3] in SoftLink._link_attrprefixes):
247 object.__setattr__(self, attrname, value)
249 # set attribute of the target node
250 elif not self._v_isopen:
251 raise tb.ClosedNodeError('the node object is closed')
252 elif self.is_dangling():
253 raise ValueError("softlink target does not exist")
254 else:
255 self.dereference().__setattr__(attrname, value)
257 def __getitem__(self, key):
258 """__getitem__ must be defined in the SoftLink class in order for array
259 indexing syntax to work"""
261 if not self._v_isopen:
262 raise tb.ClosedNodeError('the node object is closed')
263 elif self.is_dangling():
264 raise ValueError("softlink target does not exist")
265 else:
266 return self.dereference().__getitem__(key)
268 def __setitem__(self, key, value):
269 """__setitem__ must be defined in the SoftLink class in order for array
270 indexing syntax to work"""
272 if not self._v_isopen:
273 raise tb.ClosedNodeError('the node object is closed')
274 elif self.is_dangling():
275 raise ValueError("softlink target does not exist")
276 else:
277 self.dereference().__setitem__(key, value)
279 def is_dangling(self):
280 return not (self.dereference() in self._v_file)
282 def __str__(self):
283 """Return a short string representation of the link.
285 Examples
286 --------
288 ::
290 >>> f = tb.open_file('tables/tests/slink.h5')
291 >>> f.root.arr2
292 /arr2 (SoftLink) -> /arr
293 >>> f.close()
295 """
297 target = str(self.target)
298 # Check for relative pathnames
299 if not self.target.startswith('/'):
300 target = self._v_parent._g_join(self.target)
301 closed = "" if self._v_isopen else "closed "
302 dangling = "" if target in self._v_file else " (dangling)"
303 return (f"{closed}{self._v_pathname} ({self.__class__.__name__}) -> "
304 f"{self.target}{dangling}")
307class ExternalLink(linkextension.ExternalLink, Link):
308 """Represents an external link.
310 An external link is a reference to a node in *another* file.
311 Getting access to the pointed node (this action is called
312 *dereferencing*) is done via the :meth:`__call__` special method
313 (see below).
315 .. rubric:: ExternalLink attributes
317 .. attribute:: extfile
319 The external file handler, if the link has been dereferenced.
320 In case the link has not been dereferenced yet, its value is
321 None.
323 """
325 # Class identifier.
326 _c_classid = 'EXTERNALLINK'
328 def __init__(self, parentnode, name, target=None, _log=False):
329 self.extfile = None
330 """The external file handler, if the link has been dereferenced.
331 In case the link has not been dereferenced yet, its value is
332 None."""
333 super().__init__(parentnode, name, target, _log)
335 def _get_filename_node(self):
336 """Return the external filename and nodepath from `self.target`."""
338 # This is needed for avoiding the 'C:\\file.h5' filepath notation
339 filename, target = self.target.split(':/')
340 return filename, '/' + target
342 def __call__(self, **kwargs):
343 """Dereference self.target and return the object.
345 You can pass all the arguments supported by the :func:`open_file`
346 function (except filename, of course) so as to open the referenced
347 external file.
349 Examples
350 --------
352 ::
354 >>> f = tb.open_file('tables/tests/elink.h5')
355 >>> f.root.pep.pep2
356 /pep/pep2 (ExternalLink) -> elink2.h5:/pep
357 >>> pep2 = f.root.pep.pep2(mode='r') # open in 'r'ead mode
358 >>> print(pep2)
359 /pep (Group) ''
360 >>> pep2._v_file.filename # belongs to referenced file
361 'tables/tests/elink2.h5'
362 >>> f.close()
364 """
366 filename, target = self._get_filename_node()
368 if not Path(filename).is_absolute():
369 # Resolve the external link with respect to the this
370 # file's directory. See #306.
371 filename = str(Path(self._v_file.filename).with_name(filename))
373 if self.extfile is None or not self.extfile.isopen:
374 self.extfile = tb.open_file(filename, **kwargs)
375 else:
376 # XXX: implement better consistency checks
377 assert self.extfile.filename == filename
378 assert self.extfile.mode == kwargs.get('mode', 'r')
380 return self.extfile._get_node(target)
382 def umount(self):
383 """Safely unmount self.extfile, if opened."""
385 extfile = self.extfile
386 # Close external file, if open
387 if extfile is not None and extfile.isopen:
388 extfile.close()
389 self.extfile = None
391 def _f_close(self):
392 """Especific close for external links."""
394 self.umount()
395 super()._f_close()
397 def __str__(self):
398 """Return a short string representation of the link.
400 Examples
401 --------
403 ::
405 >>> f = tb.open_file('tables/tests/elink.h5')
406 >>> f.root.pep.pep2
407 /pep/pep2 (ExternalLink) -> elink2.h5:/pep
408 >>> f.close()
410 """
412 return (f"{self._v_pathname} ({self.__class__.__name__}) -> "
413 f"{self.target}")