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

1"""Create links in the HDF5 file. 

2 

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). 

6 

7Classes: 

8 

9 SoftLink 

10 ExternalLink 

11 

12Functions: 

13 

14Misc variables: 

15 

16""" 

17 

18from pathlib import Path 

19 

20import tables as tb 

21 

22from . import linkextension 

23from .node import Node 

24from .utils import lazyattr 

25from .attributeset import AttributeSet 

26 

27 

28def _g_get_link_class(parent_id, name): 

29 """Guess the link class.""" 

30 

31 return linkextension._get_link_class(parent_id, name) 

32 

33 

34class Link(Node): 

35 """Abstract base class for all PyTables links. 

36 

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. 

43 

44 See :ref:`LinksTutorial` for a small tutorial on how to work with links. 

45 

46 .. rubric:: Link attributes 

47 

48 .. attribute:: target 

49 

50 The path string to the pointed node. 

51 

52 """ 

53 

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__) 

66 

67 def __setattr__(self, name, value): 

68 raise KeyError("you cannot set attributes to this " 

69 "`%s` instance" % self.__class__.__name__) 

70 

71 def _g_close(self): 

72 pass 

73 return NoAttrs(self) 

74 

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.""" 

79 

80 super().__init__(parentnode, name, _log) 

81 

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. 

86 

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. 

90 

91 """ 

92 

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 

99 

100 def move(self, newparent=None, newname=None, overwrite=False): 

101 """Move or rename this link. 

102 

103 See :meth:`Node._f_move` for a complete explanation of the arguments. 

104 

105 """ 

106 

107 return self._f_move(newparent=newparent, newname=newname, 

108 overwrite=overwrite) 

109 

110 def remove(self): 

111 """Remove this link from the hierarchy.""" 

112 

113 return self._f_remove() 

114 

115 def rename(self, newname=None, overwrite=False): 

116 """Rename this link in place. 

117 

118 See :meth:`Node._f_rename` for a complete explanation of the arguments. 

119 

120 """ 

121 

122 return self._f_rename(newname=newname, overwrite=overwrite) 

123 

124 def __repr__(self): 

125 return str(self) 

126 

127 

128class SoftLink(linkextension.SoftLink, Link): 

129 """Represents a soft link (aka symbolic link). 

130 

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. 

134 

135 Softlinks also have the following public methods/attributes: 

136 

137 * `target` 

138 * `dereference()` 

139 * `copy()` 

140 * `move()` 

141 * `remove()` 

142 * `rename()` 

143 * `is_dangling()` 

144 

145 Note that these will override any correspondingly named methods/attributes 

146 of the target node. 

147 

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) 

151 

152 Examples 

153 -------- 

154 

155 :: 

156 

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') 

161 

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')) 

166 

167 # dereferencing a softlink using the __call__() method 

168 >>> link_a() is a 

169 True 

170 

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() 

178 

179 

180 """ 

181 

182 # Class identifier. 

183 _c_classid = 'SOFTLINK' 

184 

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_') 

191 

192 def __call__(self): 

193 """Dereference `self.target` and return the object. 

194 

195 Examples 

196 -------- 

197 

198 :: 

199 

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() 

206 

207 """ 

208 return self.dereference() 

209 

210 def dereference(self): 

211 

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 

220 

221 def __getattribute__(self, attrname): 

222 

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) 

227 

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) 

241 

242 def __setattr__(self, attrname, value): 

243 

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) 

248 

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) 

256 

257 def __getitem__(self, key): 

258 """__getitem__ must be defined in the SoftLink class in order for array 

259 indexing syntax to work""" 

260 

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) 

267 

268 def __setitem__(self, key, value): 

269 """__setitem__ must be defined in the SoftLink class in order for array 

270 indexing syntax to work""" 

271 

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) 

278 

279 def is_dangling(self): 

280 return not (self.dereference() in self._v_file) 

281 

282 def __str__(self): 

283 """Return a short string representation of the link. 

284 

285 Examples 

286 -------- 

287 

288 :: 

289 

290 >>> f = tb.open_file('tables/tests/slink.h5') 

291 >>> f.root.arr2 

292 /arr2 (SoftLink) -> /arr 

293 >>> f.close() 

294 

295 """ 

296 

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}") 

305 

306 

307class ExternalLink(linkextension.ExternalLink, Link): 

308 """Represents an external link. 

309 

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). 

314 

315 .. rubric:: ExternalLink attributes 

316 

317 .. attribute:: extfile 

318 

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. 

322 

323 """ 

324 

325 # Class identifier. 

326 _c_classid = 'EXTERNALLINK' 

327 

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) 

334 

335 def _get_filename_node(self): 

336 """Return the external filename and nodepath from `self.target`.""" 

337 

338 # This is needed for avoiding the 'C:\\file.h5' filepath notation 

339 filename, target = self.target.split(':/') 

340 return filename, '/' + target 

341 

342 def __call__(self, **kwargs): 

343 """Dereference self.target and return the object. 

344 

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. 

348 

349 Examples 

350 -------- 

351 

352 :: 

353 

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() 

363 

364 """ 

365 

366 filename, target = self._get_filename_node() 

367 

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)) 

372 

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') 

379 

380 return self.extfile._get_node(target) 

381 

382 def umount(self): 

383 """Safely unmount self.extfile, if opened.""" 

384 

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 

390 

391 def _f_close(self): 

392 """Especific close for external links.""" 

393 

394 self.umount() 

395 super()._f_close() 

396 

397 def __str__(self): 

398 """Return a short string representation of the link. 

399 

400 Examples 

401 -------- 

402 

403 :: 

404 

405 >>> f = tb.open_file('tables/tests/elink.h5') 

406 >>> f.root.pep.pep2 

407 /pep/pep2 (ExternalLink) -> elink2.h5:/pep 

408 >>> f.close() 

409 

410 """ 

411 

412 return (f"{self._v_pathname} ({self.__class__.__name__}) -> " 

413 f"{self.target}")