# -*- coding: utf-8 -*-
########################################################################
#
# License: BSD
# Created: November 25, 2009
# Author: Francesc Alted - faltet@pytables.com
#
# $Id$
#
########################################################################
"""Create links in the HDF5 file.
This module implements containers for soft and external links. Hard
links doesn't need a container as such as they are the same as regular
nodes (groups or leaves).
Classes:
SoftLink
ExternalLink
Functions:
Misc variables:
"""
import os
import tables as t
from tables import linkextension
from tables.node import Node
from tables.utils import lazyattr
from tables.attributeset import AttributeSet
import tables.file
from tables._past import previous_api, previous_api_property
def _g_get_link_class(parent_id, name):
"""Guess the link class."""
return linkextension._get_link_class(parent_id, name)
_g_getLinkClass = previous_api(_g_get_link_class)
[docs]class Link(Node):
"""Abstract base class for all PyTables links.
A link is a node that refers to another node. The Link class inherits from
Node class and the links that inherits from Link are SoftLink and
ExternalLink. There is not a HardLink subclass because hard links behave
like a regular Group or Leaf. Contrarily to other nodes, links cannot have
HDF5 attributes. This is an HDF5 library limitation that might be solved
in future releases.
See :ref:`LinksTutorial` for a small tutorial on how to work with links.
.. rubric:: Link attributes
.. attribute:: target
The path string to the pointed node.
"""
# Properties
@lazyattr
[docs] def _v_attrs(self):
"""
A *NoAttrs* instance replacing the typical *AttributeSet* instance of
other node objects. The purpose of *NoAttrs* is to make clear that
HDF5 attributes are not supported in link nodes.
"""
class NoAttrs(AttributeSet):
def __getattr__(self, name):
raise KeyError("you cannot get attributes from this "
"`%s` instance" % self.__class__.__name__)
def __setattr__(self, name, value):
raise KeyError("you cannot set attributes to this "
"`%s` instance" % self.__class__.__name__)
def _g_close(self):
pass
return NoAttrs(self)
def __init__(self, parentnode, name, target=None, _log=False):
self._v_new = target is not None
self.target = target
"""The path string to the pointed node."""
super(Link, self).__init__(parentnode, name, _log)
# Public and tailored versions for copy, move, rename and remove methods
[docs] def copy(self, newparent=None, newname=None,
overwrite=False, createparents=False):
"""Copy this link and return the new one.
See :meth:`Node._f_copy` for a complete explanation of the arguments.
Please note that there is no recursive flag since links do not have
child nodes.
"""
newnode = self._f_copy(newparent=newparent, newname=newname,
overwrite=overwrite,
createparents=createparents)
# Insert references to a `newnode` via `newname`
newnode._v_parent._g_refnode(newnode, newname, True)
return newnode
[docs] def move(self, newparent=None, newname=None, overwrite=False):
"""Move or rename this link.
See :meth:`Node._f_move` for a complete explanation of the arguments.
"""
return self._f_move(newparent=newparent, newname=newname,
overwrite=overwrite)
[docs] def remove(self):
"""Remove this link from the hierarchy."""
return self._f_remove()
[docs] def rename(self, newname=None, overwrite=False):
"""Rename this link in place.
See :meth:`Node._f_rename` for a complete explanation of the arguments.
"""
return self._f_rename(newname=newname, overwrite=overwrite)
def __repr__(self):
return str(self)
[docs]class SoftLink(linkextension.SoftLink, Link):
"""Represents a soft link (aka symbolic link).
A soft link is a reference to another node in the *same* file hierarchy.
Getting access to the pointed node (this action is called *dereferrencing*)
is done via the __call__ special method (see below).
"""
# Class identifier.
_c_classid = 'SOFTLINK'
_c_classId = previous_api_property('_c_classid')
[docs] def __call__(self):
"""Dereference `self.target` and return the object.
Examples
--------
::
>>> f=tables.open_file('data/test.h5')
>>> print f.root.link0
/link0 (SoftLink) -> /another/path
>>> print f.root.link0()
/another/path (Group) ''
"""
target = self.target
# Check for relative pathnames
if not self.target.startswith('/'):
target = self._v_parent._g_join(self.target)
return self._v_file._get_node(target)
[docs] def __str__(self):
"""Return a short string representation of the link.
Examples
--------
::
>>> f=tables.open_file('data/test.h5')
>>> print f.root.link0
/link0 (SoftLink) -> /path/to/node
"""
classname = self.__class__.__name__
target = self.target
# Check for relative pathnames
if not self.target.startswith('/'):
target = self._v_parent._g_join(self.target)
if target in self._v_file:
dangling = ""
else:
dangling = " (dangling)"
return "%s (%s) -> %s%s" % (self._v_pathname, classname,
self.target, dangling)
[docs]class ExternalLink(linkextension.ExternalLink, Link):
"""Represents an external link.
An external link is a reference to a node in *another* file.
Getting access to the pointed node (this action is called
*dereferencing*) is done via the :meth:`__call__` special method
(see below).
.. rubric:: ExternalLink attributes
.. attribute:: extfile
The external file handler, if the link has been dereferenced.
In case the link has not been dereferenced yet, its value is
None.
"""
# Class identifier.
_c_classid = 'EXTERNALLINK'
_c_classId = previous_api_property('_c_classid')
def __init__(self, parentnode, name, target=None, _log=False):
self.extfile = None
"""The external file handler, if the link has been dereferenced.
In case the link has not been dereferenced yet, its value is
None."""
super(ExternalLink, self).__init__(parentnode, name, target, _log)
def _get_filename_node(self):
"""Return the external filename and nodepath from `self.target`."""
# This is needed for avoiding the 'C:\\file.h5' filepath notation
filename, target = self.target.split(':/')
return filename, '/' + target
[docs] def __call__(self, **kwargs):
"""Dereference self.target and return the object.
You can pass all the arguments supported by the :func:`open_file`
function (except filename, of course) so as to open the referenced
external file.
Examples
--------
::
>>> f=tables.open_file('data1/test1.h5')
>>> print f.root.link2
/link2 (ExternalLink) -> data2/test2.h5:/path/to/node
>>> plink2 = f.root.link2('a') # open in 'a'ppend mode
>>> print plink2
/path/to/node (Group) ''
>>> print plink2._v_filename
'data2/test2.h5' # belongs to referenced file
"""
filename, target = self._get_filename_node()
if not os.path.isabs(filename):
# Resolve the external link with respect to the this
# file's directory. See #306.
base_directory = os.path.dirname(self._v_file.filename)
filename = os.path.join(base_directory, filename)
# Fetch the external file and save a reference to it.
# Check first in already opened files.
open_files = tables.file._open_files
if filename in open_files:
self.extfile = open_files[filename]
else:
self.extfile = t.open_file(filename, **kwargs)
return self.extfile._get_node(target)
[docs] def umount(self):
"""Safely unmount self.extfile, if opened."""
extfile = self.extfile
# Close external file, if open
if extfile is not None and extfile.isopen:
extfile.close()
self.extfile = None
def _f_close(self):
"""Especific close for external links."""
self.umount()
super(ExternalLink, self)._f_close()
[docs] def __str__(self):
"""Return a short string representation of the link.
Examples
--------
::
>>> f=tables.open_file('data1/test1.h5')
>>> print f.root.link2
/link2 (ExternalLink) -> data2/test2.h5:/path/to/node
"""
classname = self.__class__.__name__
return "%s (%s) -> %s" % (self._v_pathname, classname, self.target)
## Local Variables:
## mode: python
## py-indent-offset: 4
## tab-width: 4
## fill-column: 72
## End: