37
__version__ = "$Revision: 1.91.2.3 $"
41
import tables.lrucache
42
import tables.hdf5Extension as hdf5Extension
43
import tables.utilsExtension as utilsExtension
44
import tables.proxydict
45
from tables.constants import \
46
MAX_UNDO_PATH_LENGTH, METADATA_CACHE_SIZE, NODE_CACHE_SIZE
47
from tables.exceptions import \
48
ClosedFileError, FileModeError, \
49
NodeError, NoSuchNodeError, \
50
UndoRedoError, UndoRedoWarning
51
from tables.utils import joinPath, splitPath, isVisiblePath, \
52
checkFileAccess, getClassByName
53
import tables.undoredo as undoredo
54
from tables.IsDescription import IsDescription, UInt8Col, StringCol
55
from tables.Node import Node
56
from tables.Group import Group, RootGroup
57
from tables.Group import TransactionGroupG, TransactionG, MarkG
58
from tables.Leaf import Leaf, Filters
59
from tables.Table import Table
60
from tables.Array import Array
61
from tables.CArray import CArray
62
from tables.EArray import EArray
63
from tables.VLArray import VLArray
67
__version__ = "$Revision: 1509 $"
38
70
#format_version = "1.0" # Initial format
39
71
#format_version = "1.1" # Changes in ucl compression
40
72
#format_version = "1.2" # Support for enlargeable arrays and VLA's
41
73
# 1.2 was introduced in pytables 0.8
42
format_version = "1.3" # Support for indexes in Tables
74
#format_version = "1.3" # Support for indexes in Tables
43
75
# 1.3 was introduced in pytables 0.9
76
#format_version = "1.4" # Support for multidimensional attributes
77
# # 1.4 was introduced in pytables 1.1
78
#format_version = "1.5" # Support for persistent defaults in tables
79
# # 1.5 was introduced in pytables 1.2
80
format_version = "1.6" # Support for NumPy objects and new flavors for objects
81
# 1.6 was introduced in pytables 1.3
44
82
compatible_formats = [] # Old format versions we can read
45
83
# Empty means that we support all the old formats
47
from __future__ import generators
54
from fnmatch import fnmatch
58
from Group import Group
59
from Leaf import Leaf, Filters
60
from Table import Table
61
from Array import Array
62
from EArray import EArray
63
from VLArray import VLArray
64
from UnImplemented import UnImplemented
65
from AttributeSet import AttributeSet
85
# Dict of opened files (keys are filehandlers and values filenames)
88
# Opcodes for do-undo actions
89
_opToCode = {"MARK": 0,
97
_codeToOp = ["MARK", "CREATE", "REMOVE", "MOVE", "ADDATTR", "DELATTR"]
100
# Paths and names for hidden nodes related with transactions.
101
_transVersion = '1.0'
103
_transGroupParent = '/'
104
_transGroupName = '_p_transactions'
105
_transGroupPath = joinPath(_transGroupParent, _transGroupName)
107
_actionLogParent = _transGroupPath
108
_actionLogName = 'actionlog'
109
_actionLogPath = joinPath(_actionLogParent, _actionLogName)
111
_transParent = _transGroupPath
112
_transName = 't%d' # %d -> transaction number
113
_transPath = joinPath(_transParent, _transName)
115
_markParent = _transPath
116
_markName = 'm%d' # %d -> mark number
117
_markPath = joinPath(_markParent, _markName)
119
_shadowParent = _markPath
120
_shadowName = 'a%d' # %d -> action number
121
_shadowPath = joinPath(_shadowParent, _shadowName)
68
125
def _checkFilters(filters, compress=None, complib=None):
69
126
if (filters is None) and ((compress is not None) or
70
127
(complib is not None)):
71
warnings.warn("The use of compress or complib parameters is deprecated. Please, use a Filters() instance instead.", DeprecationWarning)
128
warnings.warn(DeprecationWarning("""\
129
``compress`` and ``complib`` parameters are deprecated; \
130
please use a ``Filters`` instance instead"""),
72
132
fprops = Filters(complevel=compress, complib=complib)
73
133
elif filters is None:
75
135
elif isinstance(filters, Filters):
79
"filter parameter has to be None or a Filter instance and the passed type is: '%s'" % type(filters)
138
raise TypeError, "filter parameter has to be None or a Filter instance and the passed type is: '%s'" % type(filters)
83
def copyFile(srcFilename = None, dstFilename=None, title=None,
84
filters=None, copyuserattrs=1, overwrite=0):
85
"""Copy srcFilename to dstFilename
87
The "srcFilename" should exist and "dstFilename" should not. But
88
if "dsFilename" exists and "overwrite" is true, it is
89
overwritten. "title" lets you put another title to the destination
90
file. "copyuserattrs" specifies whether the user attrs in origin
91
nodes should be copied or not; the default is copy them. Finally,
92
specifiying a "filters" parameter overrides the original filter
93
properties of nodes in "srcFilename".
95
It returns the number of copied groups, leaves and bytes in the
96
form (ngroups, nleaves, nbytes).
101
srcFileh = openFile(srcFilename, mode="r")
103
# Copy it to the destination
104
ngroups, nleaves, nbytes = srcFileh.copyFile(dstFilename, title=title,
106
copyuserattrs=copyuserattrs,
109
# Close the source file
111
return ngroups, nleaves, nbytes
142
def copyFile(srcfilename, dstfilename, overwrite=False, **kwargs):
144
An easy way of copying one PyTables file to another.
146
This function allows you to copy an existing PyTables file named
147
`srcfilename` to another file called `dstfilename`. The source file
148
must exist and be readable. The destination file can be overwritten
149
in place if existing by asserting the `overwrite` argument.
151
This function is a shorthand for the `File.copyFile()` method, which
152
acts on an already opened file. `kwargs` takes keyword arguments
153
used to customize the copying process. See the documentation of
154
`File.copyFile()` for a description of those arguments.
157
# Open the source file.
158
srcFileh = openFile(srcfilename, mode="r")
161
# Copy it to the destination file.
162
srcFileh.copyFile(dstfilename, overwrite=overwrite, **kwargs)
164
# Close the source file.
114
168
def openFile(filename, mode="r", title="", trMap={}, rootUEP="/",
169
filters=None, nodeCacheSize=NODE_CACHE_SIZE):
117
"""Open an HDF5 file an returns a File object.
171
"""Open an HDF5 file and return a File object.
154
208
filters properties are specified for these leaves, of
155
209
course). Besides, if you do not specify filter properties
156
210
for its child groups, they will inherit these ones.
212
nodeCacheSize -- The number of *unreferenced* nodes to be kept in
213
memory. Least recently used nodes are unloaded from memory
214
when this number of loaded nodes is reached. To load a node
215
again, simply access it as usual. Nodes referenced by user
216
variables are not taken into account nor unloaded.
160
isPTFile = 1 # Assume a PyTables file by default
161
220
# Expand the form '~user'
162
221
path = os.path.expanduser(filename)
163
222
# Expand the environment variables
164
223
path = os.path.expandvars(path)
166
# The file extension warning commmented out by people at GL suggestion
168
# if not (fnmatch(path, "*.h5") or
169
# fnmatch(path, "*.hdf") or
170
# fnmatch(path, "*.hdf5")):
172
# """filename '%s'should have one of the next file extensions
173
# '.h5', '.hdf' or '.hdf5'. Continuing anyway.""" % path, UserWarning)
175
# Only accept modes 'w', 'r', 'r+' or 'a'
176
assert mode in ['w', 'r', 'r+', 'a'], \
177
"""arg 2 must take one of this values: ['w', 'r', 'r+', 'a']"""
179
if (mode == "r" or mode == "r+"):
180
# For 'r' and 'r+' check that path exists and is a HDF5 file
181
if not os.path.isfile(path):
183
"""'%s' pathname does not exist or is not a regular file""" % path
185
if not hdf5Extension.isHDF5(path):
187
"""'%s' does exist but it is not an HDF5 file""" % path
189
elif not hdf5Extension.isPyTablesFile(path):
191
"""\n'%s' does exist, is an HDF5 file, but has not a PyTables format. Trying to guess what's there using HDF5 metadata. I can't promise you getting the correct objects, but I will do my best!.""" % \
197
# For 'w' check that if path exists, and if true, delete it!
198
if os.path.isfile(path):
199
# Delete the old file
202
if os.path.isfile(path):
203
if not hdf5Extension.isHDF5(path):
205
"""'%s' does exist but it is not an HDF5 file""" % path
207
elif not hdf5Extension.isPyTablesFile(path):
209
"""'%s' does exist, is an HDF5 file, but has not a PyTables format.
210
Trying to guess what's here from HDF5 metadata. I can't promise you getting
211
the correct object, but I will do my best!.""" % path, UserWarning)
215
"""arg 2 can only take the new values: "r", "r+", "w" and "a" """
217
# new informs if this file is old or new
220
(mode == "a" and os.path.isfile(path)) ):
225
225
# Finally, create the File instance, and return it
226
return File(path, mode, title, new, trMap, rootUEP, isPTFile, filters)
226
return File(path, mode, title, trMap, rootUEP, filters,
227
METADATA_CACHE_SIZE, nodeCacheSize)
230
class _AliveNodes(dict):
232
"""Stores weak references to nodes in a transparent way."""
234
def __getitem__(self, key):
235
ref = super(_AliveNodes, self).__getitem__(key)
238
def __setitem__(self, key, value):
239
ref = weakref.ref(value)
240
super(_AliveNodes, self).__setitem__(key, ref)
244
class _DeadNodes(tables.lrucache.LRUCache):
252
class _NodeDict(tables.proxydict.ProxyDict):
255
A proxy dictionary which is able to delegate access to missing items
256
to the container object (a `File`).
259
def _getValueFromContainer(self, container, key):
260
return container.getNode(key)
263
def _condition(self, node):
264
"""Nodes fulfilling the condition are considered to belong here."""
265
raise NotImplementedError
268
def _warnOnGet(self):
269
warnings.warn("using this mapping object is deprecated; "
270
"please use ``File.getNode()`` instead",
274
def __contains__(self, key):
277
# If the key is here there is nothing else to check.
278
if super(_NodeDict, self).__contains__(key):
281
# Look if the key is in the container `File`.
283
file_ = self._getContainer()
284
node = file_.getNode(key)
285
# Does it fullfill the condition?
286
return self._condition(node)
287
except NoSuchNodeError:
288
# It is not in the container.
292
def __getitem__(self, key):
294
return super(_NodeDict, self).__getitem__(key)
297
# The following operations are quite underperforming
298
# because they need to browse the entire tree.
299
# These objects are deprecated, anyway.
302
return self.iterkeys()
306
warnings.warn("using this mapping object is deprecated; "
307
"please use ``File.walkNodes()`` instead",
309
for node in self._getContainer().walkNodes('/', self._className):
310
yield node._v_pathname
316
for nodePath in self.iterkeys():
321
class _ObjectsDict(_NodeDict):
323
"""Maps all visible objects."""
327
def _condition(self, node):
328
return isVisiblePath(node._v_pathname)
331
class _GroupsDict(_NodeDict):
333
"""Maps all visible groups."""
337
def _condition(self, node):
338
return isVisiblePath(node._v_pathname) and isinstance(node, Group)
341
class _LeavesDict(_NodeDict):
343
"""Maps all visible leaves."""
347
def _condition(self, node):
348
return isVisiblePath(node._v_pathname) and isinstance(node, Leaf)
229
352
class File(hdf5Extension.File, object):
230
"""Returns an object describing the file in-memory.
232
File class offer methods to browse the object tree, to create new
233
nodes, to rename them, to delete as well as to assign and read
238
createGroup(where, name[, title] [, filters])
239
createTable(where, name, description [, title]
240
[, filters] [, expectedrows])
241
createArray(where, name, arrayObject, [, title])
242
createEArray(where, name, object [, title]
243
[, filters] [, expectedrows])
244
createVLArray(where, name, atom [, title]
245
[, filters] [, expectedsizeinMB])
246
getNode(where [, name] [,classname])
247
listNodes(where [, classname])
248
removeNode(where [, name] [, recursive])
249
renameNode(where, newname [, name])
250
getAttrNode(self, where, attrname [, name])
251
setAttrNode(self, where, attrname, attrname [, name])
252
delAttrNode(self, where, attrname [, name])
253
copyAttrs(self, where, name, dstNode)
254
copyChildren(self, whereSrc, whereDst [, recursive] [, filters]
255
[, copyuserattrs] [, start] [, stop ] [, step]
257
copyFile(self, dstFilename [, title]
258
[, filters] [, copyuserattrs] [, overwrite])
260
walkNodes([where] [, classname])
355
In-memory representation of a PyTables file.
357
An instance of this class is returned when a PyTables file is opened
358
with the `openFile()` function. It offers methods to manipulate
359
(create, rename, delete...) nodes and handle their attributes, as
360
well as methods to traverse the object tree. The *user entry point*
361
to the object tree attached to the HDF5 file is represented in the
362
``rootUEP`` attribute. Other attributes are available.
364
`File` objects support an *Undo/Redo mechanism* which can be enabled
365
with the `enableUndo()` method. Once the Undo/Redo mechanism is
366
enabled, explicit *marks* (with an optional unique name) can be set
367
on the state of the database using the `mark()` method. There are
368
two implicit marks which are always available: the initial mark (0)
369
and the final mark (-1). Both the identifier of a mark and its name
370
can be used in *undo* and *redo* operations.
372
Hierarchy manipulation operations (node creation, movement and
373
removal) and attribute handling operations (attribute setting and
374
deleting) made after a mark can be undone by using the `undo()`
375
method, which returns the database to the state of a past mark. If
376
`undo()` is not followed by operations that modify the hierarchy or
377
attributes, the `redo()` method can be used to return the database
378
to the state of a future mark. Else, future states of the database
381
Please note that data handling operations can not be undone nor
382
redone by now. Also, hierarchy manipulation operations on nodes
383
that do not support the Undo/Redo mechanism issue an
384
`UndoRedoWarning` *before* changing the database.
386
The Undo/Redo mechanism is persistent between sessions and can only
387
be disabled by calling the `disableUndo()` method.
264
389
Instance variables:
266
filename -- filename opened
267
format_version -- The PyTables version number of this file
268
isopen -- 1 if the underlying file is still open; 0 if not
269
mode -- mode in which the filename was opened
270
title -- the title of the root group in file
271
root -- the root group in file
272
rootUEP -- the root User Entry Point group in file
273
trMap -- the mapping between python and HDF5 domain names
274
objects -- dictionary with all objects (groups or leaves) on tree
275
groups -- dictionary with all object groups on tree
276
leaves -- dictionary with all object leaves on tree
392
The name of the opened file.
394
The PyTables version number of this file.
396
True if the underlying file is open, false otherwise.
398
The mode in which the file was opened.
400
The title of the root group in the file.
402
A dictionary that maps node names between PyTables and HDF5
403
domain names. Its initial values are set from the ``trMap``
404
parameter passed to the `openFile()` function. You can change
405
its contents *after* a file is opened and the new map will take
406
effect over any new object added to the tree.
408
The UEP (user entry point) group in the file (see the
409
`openFile()` function).
411
Default filter properties for the root group (see the `Filters`
414
The *root* of the object tree hierarchy (a `Group` instance).
416
A dictionary which maps path names to objects, for every visible
417
node in the tree (deprecated, see note below).
419
A dictionary which maps path names to objects, for every visible
420
group in the tree (deprecated, see note below).
422
A dictionary which maps path names to objects, for every visible
423
leaf in the tree (deprecated, see note below).
427
From PyTables 1.2 on, the dictionaries ``objects``, ``groups``
428
and ``leaves`` are just instances of objects faking the old
429
functionality. Actually, they internally use ``File.getNode()``
430
and ``File.walkNodes()``, which are recommended instead.
433
Public methods (file handling):
435
* copyFile(dstfilename[, overwrite][, **kwargs])
439
Public methods (hierarchy manipulation):
441
* createGroup(where, name[, title][, filters])
442
* createTable(where, name, description[, title][, filters]
444
* createArray(where, name, array[, title])
445
* createEArray(where, name, atom[, title][, filters]
447
* createVLArray(where, name, atom[, title][, filters]
448
[, expectedsizeinMB])
449
* removeNode(where[, name][, recursive])
450
* renameNode(where, newname[, name])
451
* moveNode(where, newparent, newname[, name][, overwrite])
452
* copyNode(where, newparent, newname[, name][, overwrite]
453
[, recursive][, **kwargs])
454
* copyChildren(srcgroup, dstgroup[, overwrite][, recursive]
457
Public methods (tree traversal):
459
* getNode(where[, name][,classname])
460
* isVisibleNode(path)
461
* listNodes(where[, classname])
462
* walkGroups([where])
463
* walkNodes([where][, classname])
466
Public methods (Undo/Redo support):
469
Is the Undo/Redo mechanism enabled?
470
enableUndo([filters])
471
Enable the Undo/Redo mechanism.
473
Disable the Undo/Redo mechanism.
475
Mark the state of the database.
477
Get the identifier of the current mark.
479
Go to a past state of the database.
481
Go to a future state of the database.
483
Go to a specific mark of the database.
485
Public methods (attribute handling):
487
* getNodeAttr(where, attrname[, name])
488
* setNodeAttr(where, attrname, attrvalue[, name])
489
* delNodeAttr(where, attrname[, name])
490
* copyNodeAttrs(where, dstnode[, name])
280
def __init__(self, filename, mode="r", title="", new=1, trMap={},
281
rootUEP="/", isPTFile=1, filters=None):
496
return self.root._v_title
497
def _settitle(self, title):
498
self.root._v_title = title
500
del self.root._v_title
502
title = property(_gettitle, _settitle, _deltitle,
503
"The title of the root group in the file.")
505
def _getfilters(self):
506
return self.root._v_filters
507
def _setfilters(self, filters):
508
self.root._v_filters = filters
509
def _delfilters(self):
510
del self.root._v_filters
512
filters = property(_getfilters, _setfilters, _delfilters,
513
"Default filter properties for the root group "
514
"(see the `Filters` class).")
519
def __init__(self, filename, mode="r", title="", trMap={},
520
rootUEP="/", filters=None,
521
metadataCacheSize=METADATA_CACHE_SIZE,
522
nodeCacheSize=NODE_CACHE_SIZE):
283
523
"""Open an HDF5 file. The supported access modes are: "r" means
284
524
read-only; no data can be modified. "w" means write; a new file is
285
525
created, an existing file with the same name is deleted. "a" means
289
529
TITLE attribute will be set on the root group if optional "title"
290
530
parameter is passed."""
292
534
self.filename = filename
293
#print "Opening the %s HDF5 file ...." % self.filename
297
self._isPTFile = isPTFile
299
# _v_new informs if this file is old or new
537
# Nodes referenced by a variable are kept in `_aliveNodes`.
538
# When they are no longer referenced, they move themselves
539
# to `_deadNodes`, where they are kept until they are referenced again
540
# or they are preempted from it by other unreferenced nodes.
541
self._aliveNodes = _AliveNodes()
542
self._deadNodes = _DeadNodes(nodeCacheSize)
544
# The following dictionaries map paths to different kinds of nodes.
545
# In fact, they store nothing but the keys; the real nodes
546
# are obtained using `self.getNode()`.
547
self.objects = _ObjectsDict(self)
549
A dictionary which maps path names to objects, for every visible
550
node in the tree (deprecated).
552
self.groups = _GroupsDict(self)
554
A dictionary which maps path names to objects, for every visible
555
group in the tree (deprecated).
557
self.leaves = _LeavesDict(self)
559
A dictionary which maps path names to objects, for every visible
560
leaf in the tree (deprecated).
301
563
# Assign the trMap and build the reverse translation
302
564
self.trMap = trMap
567
for (ptname, h5name) in self._pttoh5.iteritems():
568
if h5name in self._h5topt:
570
"the translation map has a duplicate HDF5 name %r"
572
self._h5topt[h5name] = ptname
574
# For the moment Undo/Redo is not enabled.
575
self._undoEnabled = False
308
self.filters = Filters()
310
self.filters = filters
580
if new and filters is None:
584
# Set the flag to indicate that the file has been opened.
585
# It must be set before opening the root group
586
# to allow some basic access to its attributes.
589
# Append the name of the file to the global dict of files opened.
590
_open_files[self] = self.filename
312
592
# Get the root group from this file
313
self.root = self.__getRootGroup(rootUEP)
315
# Set the flag to indicate that the file has been opened
321
def __getRootGroup(self, rootUEP):
593
self.root = root = self.__getRootGroup(rootUEP, title, filters)
594
# Complete the creation of the root node
595
# (see the explanation in ``RootGroup.__init__()``.
596
root._g_postInitHook()
598
# Save the PyTables format version for this file.
600
self.format_version = format_version
601
root._v_attrs._g__setattr(
602
'PYTABLES_FORMAT_VERSION', format_version)
604
# If the file is old, and not opened in "read-only" mode,
605
# check if it has a transaction log
606
if not new and self.mode != "r" and _transGroupPath in self:
607
# It does. Enable the undo.
611
def __getRootGroup(self, rootUEP, title, filters):
323
612
"""Returns a Group instance which will act as the root group
324
613
in the hierarchical tree. If file is opened in "r", "r+" or
325
614
"a" mode, and the file already exists, this method dynamically
326
615
builds a python object tree emulating the structure present on
329
global format_version
330
global compatible_formats
332
618
self._v_objectID = self._getFileId()
335
620
if rootUEP in [None, ""]:
338
622
# Save the User Entry Point in a variable class
339
623
self.rootUEP=rootUEP
341
rootname = "/" # Always the name of the root group
343
# Global dictionaries for the file paths.
344
# These are used to keep track of all the children and group objects
345
# in tree object. They are dictionaries that will use the pathnames
346
# as keys and the actual objects as values.
347
# That way we can find objects in the object tree easily and quickly.
352
rootGroup = Group(self._v_new)
354
# Create new attributes for the root Group instance
355
newattr = rootGroup.__dict__
356
newattr["_v_rootgroup"] = rootGroup # For compatibility with Group
357
newattr["_v_objectID"] = self._v_objectID
358
newattr["_v_parent"] = self
359
newattr["_v_file"] = self
360
newattr["_v_depth"] = 1
361
newattr["_v_filename"] = self.filename # Only root group has this
363
newattr["_v_name"] = rootname
364
newattr["_v_hdf5name"] = rootUEP
365
newattr["_v_pathname"] = rootname # Can be rootUEP? I don't think so
367
# Update global path variables for Group
368
self.groups["/"] = rootGroup
369
self.objects["/"] = rootGroup
371
# Open the root group. We do that always, be the file new or not
372
rootGroup._g_new(self, rootUEP)
373
newattr["_v_objectID"] = rootGroup._g_openGroup()
375
# Attach the AttributeSet attribute to the rootGroup group
376
newattr["_v_attrs"] = AttributeSet(rootGroup)
378
attrsRoot = rootGroup._v_attrs # Shortcut
381
newattr["_v_title"] = self.title
382
# Set the filters instance
383
newattr["_v_filters"] = self.filters
385
# Save the rootGroup attributes on disk
386
attrsRoot._g_setAttrStr('TITLE', self.title)
387
attrsRoot._g_setAttrStr('CLASS', "GROUP")
388
attrsRoot._g_setAttrStr('VERSION', "1.0")
389
filtersPickled = cPickle.dumps(self.filters, 0)
390
attrsRoot._g_setAttrStr('FILTERS', filtersPickled)
392
# Finally, save the PyTables format version for this file
393
self.format_version = format_version
394
attrsRoot._g_setAttrStr('PYTABLES_FORMAT_VERSION', format_version)
395
attrlist = ['TITLE','CLASS','VERSION','FILTERS',
396
'PYTABLES_FORMAT_VERSION']
397
# Add these attributes to the dictionary
398
attrsRoot._v_attrnames.extend(attrlist)
399
attrsRoot._v_attrnamessys.extend(attrlist)
401
attrsRoot._v_attrnames.sort()
402
attrsRoot._v_attrnamessys.sort()
627
# Get format version *before* getting the object tree
405
629
# Firstly, get the PyTables format version for this file
406
self.format_version = hdf5Extension.read_f_attr(self._v_objectID,
407
'PYTABLES_FORMAT_VERSION')
630
self.format_version = utilsExtension.read_f_attr(
631
self._v_objectID, 'PYTABLES_FORMAT_VERSION')
409
632
if not self.format_version or not self._isPTFile:
410
633
# PYTABLES_FORMAT_VERSION attribute is not present
411
634
self.format_version = "unknown"
413
# Get the title for the rootGroup group
414
if hasattr(attrsRoot, "TITLE"):
415
rootGroup.__dict__["_v_title"] = attrsRoot.TITLE
417
rootGroup.__dict__["_v_title"] = ""
418
# Get the title for the file
419
#self.title = hdf5Extension.read_f_attr(self._v_objectID, 'TITLE')
420
self.title = rootGroup._v_title
421
# Get the filters for the file
422
if hasattr(attrsRoot, "FILTERS"):
423
self.filters = attrsRoot.FILTERS
425
self.filters = Filters()
427
# Get all the groups recursively
428
rootGroup._g_openFile()
433
def _createNode(self, classname, where, name, *args, **kwargs):
435
"""Create a new "classname" instance with name "name" in "where"
436
location. "where" parameter can be a path string, or another group
437
instance. The rest of the parameters depends on what is required by
438
"classname" class constructors. See documentation on these classes
439
for information on this."""
441
if classname == "Group":
442
object = Group(*args, **kwargs)
443
elif classname == "Table":
444
object = Table(*args, **kwargs)
445
elif classname == "Array":
446
object = Array(*args, **kwargs)
447
elif classname == "EArray":
448
object = EArray(*args, **kwargs)
449
elif classname == "VLArray":
450
object = VLArray(*args, **kwargs)
453
"""Parameter 1 can only take 'Group', 'Table', 'Array', EArray or VLArray values."""
455
group = self.getNode(where, classname = 'Group')
456
# Put the object on the tree
457
setattr(group, name, object)
461
def createGroup(self, where, name, title = "", filters = None):
636
# Create new attributes for the root Group instance and
637
# create the object tree
638
return RootGroup(self, rootUEP, title=title, new=new, filters=filters)
641
def _ptNameFromH5Name(self, h5Name):
642
"""Get the PyTables name matching the given HDF5 name."""
645
# This code might seem inefficient but it will be rarely used.
646
for (ptName_, h5Name_) in self.trMap.iteritems():
647
if h5Name_ == h5Name:
653
def _h5NameFromPTName(self, ptName):
654
"""Get the HDF5 name matching the given PyTables name."""
655
return self.trMap.get(ptName, ptName)
658
def createGroup(self, where, name, title="", filters=None):
462
659
"""Create a new Group instance with name "name" in "where" location.
464
661
Keyword arguments:
626
845
optimize the HDF5 B-Tree creation and management process
627
846
time and the amount of memory used.
631
group = self.getNode(where, classname = 'Group')
634
"please, expecify an atom argument."
635
filters = _checkFilters(filters, compress, complib)
636
Object = VLArray(atom, title, filters, expectedsizeinMB)
637
setattr(group, name, Object)
641
def getNode(self, where, name = "", classname = ""):
643
"""Returns the object node "name" under "where" location.
645
"where" can be a path string or Group instance. If "where"
646
doesn't exists or has not a child called "name", a LookupError
647
error is raised. If "name" is a null string (""), or not
648
supplied, this method assumes to find the object in
649
"where". If a "classname" parameter is supplied, returns only
650
an instance of this class name. Allowed names in "classname"
651
are: 'Group', 'Leaf', 'Table', 'Array', 'EArray', 'VLArray'
652
and 'UnImplemented'."""
654
# To find out the caller
655
#print repr(sys._getframe(1).f_code.co_name)
656
if isinstance(where, str):
657
# Get rid of a possible trailing "/"
658
if len(where) > 1 and where[-1] == "/":
660
# This is a string pathname. Get the object ...
663
strObject = "/" + name
665
strObject = where + "/" + name
668
# Get the object pointed by strObject path
669
if strObject in self.objects:
670
object = self.objects[strObject]
672
# We didn't find the pathname in the object tree.
673
# This should be signaled as an error!.
675
"\"%s\" pathname not found in file: '%s'." % \
676
(strObject, self.filename)
678
elif isinstance(where, Group):
680
object = getattr(where, name)
684
elif isinstance(where, Leaf):
688
"""'where' parameter (with value '%s') is a Leaf instance in file '%s' so it cannot have a 'name' child node (with value '%s')""" % \
689
(where, self.filename, name)
695
raise TypeError, "Wrong 'where' parameter type (%s)." % \
698
# Finally, check if this object is a classname instance
849
parentNode = self.getNode(where) # Does the parent node exist?
850
fprops = _checkFilters(filters, compress, complib)
851
return VLArray(parentNode, name,
852
atom=atom, title=title, filters=fprops,
853
expectedsizeinMB=expectedsizeinMB)
856
def _getNode(self, nodePath):
857
# The root node is always at hand.
861
aliveNodes = self._aliveNodes
862
deadNodes = self._deadNodes
864
# Walk up the hierarchy until a node in the path is in memory.
865
parentPath = nodePath # deepest node in memory
866
pathTail = [] # subsequent children below that node
867
while parentPath != '/':
868
if parentPath in aliveNodes:
869
# The parent node is in memory and alive, so get it.
870
parentNode = aliveNodes[parentPath]
871
assert parentNode is not None, \
872
"stale weak reference to dead node ``%s``" % parentPath
874
if parentPath in deadNodes:
875
# The parent node is in memory but dead, so revive it.
876
parentNode = self._reviveNode(parentPath)
878
# Go up one level to try again.
879
(parentPath, nodeName) = splitPath(parentPath)
880
pathTail.insert(0, nodeName)
882
# We hit the root node and no parent was in memory.
883
parentNode = self.root
885
# Walk down the hierarchy until the last child in the tail is loaded.
886
node = parentNode # maybe `nodePath` was already in memory
887
for childName in pathTail:
888
# Load the node and use it as a parent for the next one in tail
889
# (it puts itself into life via `self._refNode()` when created).
890
if not isinstance(parentNode, Group):
891
#if parentNode is self: # this doesn't work well, but the
892
# derived speed-up is not too much anyways.
893
# This is the root group
894
parentPath = parentNode._v_pathname
895
raise TypeError("node ``%s`` is not a group; "
896
"it can not have a child named ``%s``"
897
% (parentPath, childName))
898
node = parentNode._g_loadChild(childName)
904
def getNode(self, where, name=None, classname=None):
906
Get the node under `where` with the given `name`.
908
`where` can be a `Node` instance or a path string leading to a
909
node. If no `name` is specified, that node is returned.
911
If a `name` is specified, this must be a string with the name of
912
a node under `where`. In this case the `where` argument can
913
only lead to a `Group` instance (else a `TypeError` is raised).
914
The node called `name` under the group `where` is returned.
916
In both cases, if the node to be returned does not exist, a
917
`NoSuchNodeError` is raised. Please note thet hidden nodes are
920
If the `classname` argument is specified, it must be the name of
921
a class derived from `Node`. If the node is found but it is not
922
an instance of that class, a `NoSuchNodeError` is also raised.
927
# For compatibility with old default arguments.
931
# Get the parent path (and maybe the node itself).
932
if isinstance(where, Node):
934
node._g_checkOpen() # the node object must be open
935
nodePath = where._v_pathname
936
elif isinstance(where, basestring): # Pyhton >= 2.3
941
"``where`` is not a string nor a node: %r" % (where,))
943
# Get the name of the child node.
946
nodePath = joinPath(nodePath, name)
948
assert node is None or node._v_pathname == nodePath
950
# Now we have the definitive node path, let us try to get the node.
952
node = self._getNode(nodePath)
954
# Finally, check whether the desired node is an instance
955
# of the expected class.
700
classobj = eval(classname)
701
if isinstance(object, classobj):
705
# This warning has been changed to a LookupError because
706
# I think it is more consistent
708
"""\n A %s() instance cannot be found at "%s". Instead, a %s() object has been found there.""" % \
709
(classname, object._v_pathname, object.__class__.__name__)
714
def renameNode(self, where, newname, name = ""):
715
"""Rename the object node "name" under "where" location.
717
"where" can be a path string or Group instance. If "where"
718
doesn't exists or has not a child called "name", a LookupError
719
error is raised. If "name" is a null string (""), or not
720
supplied, this method assumes to find the object in "where".
721
"newname" is the new name of be assigned to the node.
725
# Get the node to be renamed
726
object = self.getNode(where, name=name)
727
if isinstance(object, Group):
728
object._f_rename(newname)
730
object.rename(newname)
732
def removeNode(self, where, name = "", recursive = 0):
733
"""Removes the object node "name" under "where" location.
735
"where" can be a path string or Group instance. If "where"
736
doesn't exists or has not a child called "name", a LookupError
737
error is raised. If "name" is a null string (""), or not
738
supplied, this method assumes to find the object in "where".
739
If "recursive" is zero or not supplied, the object will be
740
removed only if it has not children. If "recursive" is true,
741
the object and all its descendents will be completely removed.
745
# Get the node to be removed
746
object = self.getNode(where, name=name)
747
if isinstance(object, Group):
748
object._f_remove(recursive)
752
def getAttrNode(self, where, attrname, name = ""):
753
"""Returns the attribute "attrname" of node "where"."name".
755
"where" can be a path string or Group instance. If "where"
756
doesn't exists or has not a child called "name", a LookupError
757
error is raised. If "name" is a null string (""), or not
758
supplied, this method assumes to find the object in "where".
759
"attrname" is the name of the attribute to get.
763
object = self.getNode(where, name=name)
764
if isinstance(object, Group):
765
return object._f_getAttr(attrname)
767
return object.getAttr(attrname)
769
def setAttrNode(self, where, attrname, attrvalue, name=""):
770
"""Set the attribute "attrname" of node "where"."name".
772
"where" can be a path string or Group instance. If "where"
773
doesn't exists or has not a child called "name", a LookupError
774
error is raised. If "name" is a null string (""), or not
775
supplied, this method assumes to find the object in "where".
776
"attrname" is the name of the attribute to set and "attrvalue"
781
object = self.getNode(where, name=name)
782
if isinstance(object, Group):
783
object._f_setAttr(attrname, attrvalue)
785
object.setAttr(attrname, attrvalue)
787
def delAttrNode(self, where, attrname, name = ""):
788
"""Delete the attribute "attrname" of node "where"."name".
790
"where" can be a path string or Group instance. If "where"
791
doesn't exists or has not a child called "name", a LookupError
792
error is raised. If "name" is a null string (""), or not
793
supplied, this method assumes to find the object in "where".
794
"attrname" is the name of the attribute to delete.
798
object = self.getNode(where, name=name)
799
if isinstance(object, Group):
800
return object._f_delAttr(attrname)
802
return object.delAttr(attrname)
804
def copyAttrs(self, where, name="", dstNode=None):
805
"""Copy the attributes from node "where"."name" to "dstNode".
807
"where" can be a path string or Group instance. If "where"
808
doesn't exist or has not a child called "name", a LookupError
809
error is raised. If "name" is a null string (""), or not
810
supplied, this method assumes to find the object in "where".
811
"dstNode" is the destination and can be either a path string
816
# Get the source node
957
class_ = getClassByName(classname)
958
if not isinstance(node, class_):
959
nPathname = node._v_pathname
960
nClassname = node.__class__.__name__
961
# This error message is right since it can never be shown
962
# for ``classname in [None, 'Node']``.
963
raise NoSuchNodeError(
964
"could not find a ``%s`` node at ``%s``; "
965
"instead, a ``%s`` node has been found there"
966
% (classname, nPathname, nClassname))
971
def isVisibleNode(self, path):
973
Is the node under `path` visible?
975
If the node does not exist, a ``NoSuchNodeError`` is raised.
978
# ``util.isVisiblePath()`` is still recommended for internal use.
979
return self.getNode(path)._f_isVisible()
982
def renameNode(self, where, newname, name=None):
984
Rename the given node in place.
986
The `where` and `name` arguments work as in `getNode()`,
987
referencing the node to be acted upon. The other arguments work
988
as in `Node._f_rename()`.
990
obj = self.getNode(where, name=name)
991
obj._f_rename(newname)
993
def moveNode(self, where, newparent=None, newname=None, name=None,
996
Move or rename the given node.
998
The `where` and `name` arguments work as in `getNode()`,
999
referencing the node to be acted upon. The other arguments work
1000
as in `Node._f_move()`.
1002
obj = self.getNode(where, name=name)
1003
obj._f_move(newparent, newname, overwrite)
1005
def copyNode(self, where, newparent=None, newname=None, name=None,
1006
overwrite=False, recursive=False, **kwargs):
1008
Copy the given node and return the new one.
1010
The `where` and `name` arguments work as in `getNode()`,
1011
referencing the node to be acted upon. The other arguments work
1012
as in `Node._f_copy()`.
1014
obj = self.getNode(where, name=name)
1015
return obj._f_copy(newparent, newname, overwrite, recursive, **kwargs)
1017
def removeNode(self, where, name=None, recursive=False):
1019
Remove the given node from the hierarchy.
1022
The `where` and `name` arguments work as in `getNode()`,
1023
referencing the node to be acted upon. The other arguments work
1024
as in `Node._f_remove()`.
1026
obj = self.getNode(where, name=name)
1027
obj._f_remove(recursive)
1030
def getAttrNode(self, where, attrname, name=None):
1032
Get a PyTables attribute from the given node.
1034
This method is deprecated; please use `getNodeAttr()`.
1038
``File.getAttrNode()`` is deprecated; please use ``File.getNodeAttr()``""",
1040
return self.getNodeAttr(where, attrname, name)
1042
def getNodeAttr(self, where, attrname, name=None):
1044
Get a PyTables attribute from the given node.
1046
The `where` and `name` arguments work as in `getNode()`,
1047
referencing the node to be acted upon. The other arguments work
1048
as in `Node._f_getAttr()`.
1050
obj = self.getNode(where, name=name)
1051
return obj._f_getAttr(attrname)
1054
def setAttrNode(self, where, attrname, attrvalue, name=None):
1056
Set a PyTables attribute for the given node.
1058
This method is deprecated; please use `setNodeAttr()`.
1062
``File.setAttrNode()`` is deprecated; please use ``File.setNodeAttr()``""",
1064
self.setNodeAttr(where, attrname, attrvalue, name)
1066
def setNodeAttr(self, where, attrname, attrvalue, name=None):
1068
Set a PyTables attribute for the given node.
1070
The `where` and `name` arguments work as in `getNode()`,
1071
referencing the node to be acted upon. The other arguments work
1072
as in `Node._f_setAttr()`.
1074
obj = self.getNode(where, name=name)
1075
obj._f_setAttr(attrname, attrvalue)
1078
def delAttrNode(self, where, attrname, name=None):
1080
Delete a PyTables attribute from the given node.
1082
This method is deprecated; please use `delNodeAttr()`.
1086
``File.delAttrNode()`` is deprecated; please use ``File.delNodeAttr()``""",
1088
self.delNodeAttr(where, attrname, name)
1090
def delNodeAttr(self, where, attrname, name=None):
1092
Delete a PyTables attribute from the given node.
1094
The `where` and `name` arguments work as in `getNode()`,
1095
referencing the node to be acted upon. The other arguments work
1096
as in `Node._f_delAttr()`.
1098
obj = self.getNode(where, name=name)
1099
obj._f_delAttr(attrname)
1102
def copyAttrs(self, where, dstnode, name=None):
1104
Copy attributes from one node to another.
1106
This method is deprecated; please use `copyNodeAttrs()`.
1110
``File.copyAttrs()`` is deprecated; please use ``File.copyNodeAttrs()``""",
1112
self.copyNodeAttrs(where, dstnode, name)
1114
def copyNodeAttrs(self, where, dstnode, name=None):
1116
Copy attributes from one node to another.
1118
The `where` and `name` arguments work as in `getNode()`,
1119
referencing the node to be acted upon. `dstnode` is the
1120
destination and can be either a path string or a `Node`
817
1123
srcObject = self.getNode(where, name=name)
818
dstObject = self.getNode(dstNode)
819
if isinstance(srcObject, Group):
820
object._v_attrs._f_copy(dstNode)
822
object.attrs._f_copy(dstNode)
824
def copyChildren(self, whereSrc, whereDst, recursive=0, filters=None,
825
copyuserattrs=1, start=0, stop=None, step=1,
827
"""(Recursively) Copy the children of a group into another location
829
"whereSrc" is the source group and "whereDst" is the
830
destination group. Both groups should exist or a LookupError
831
will be raised. They can be specified as strings or as Group
832
instances. "recursive" specifies whether the copy should
833
recurse into subgroups or not. The default is not
834
recurse. Specifiying a "filters" parameter overrides the
835
original filter properties in source nodes. You can prevent
836
the user attributes from being copied by setting
837
"copyuserattrs" to 0; the default is copy them. "start",
838
"stop" and "step" specifies the range of rows in leaves to be
839
copied; the default is to copy all the rows. "overwrite"
840
means whether the possible existing children hanging from
841
"whereDst" and having the same names than "whereSrc" children
842
should overwrite the destination nodes or not.
844
It returns the tuple (ngroups, nleaves, nbytes) that specifies
845
the number of groups, leaves and bytes, respectively, that has
846
been copied in the operation.
850
srcGroup = self.getNode(whereSrc, classname="Group")
851
ngroups, nleaves, nbytes = \
852
srcGroup._f_copyChildren(where=whereDst, recursive=recursive,
854
copyuserattrs=copyuserattrs,
855
start=start, stop=stop, step=step,
857
# return the number of objects copied as well as the nuber of bytes
858
return (ngroups, nleaves, nbytes)
860
def copyFile(self, dstFilename=None, title=None,
861
filters=None, copyuserattrs=1, overwrite=0):
862
"""Copy the contents of this file to "dstFilename".
864
"dstFilename" must be a path string. Specifiying a "filters"
865
parameter overrides the original filter properties in source
866
nodes. If "dstFilename" file already exists and overwrite is
867
1, it is overwritten. The default is not overwriting. It
868
returns a tuple (ngroups, nleaves, nbytes) specifying the
869
number of copied groups and leaves.
871
This copy also has the effect of compacting the destination
872
file during the process.
876
if os.path.isfile(dstFilename) and not overwrite:
877
raise IOError, "The file '%s' already exists and will not be overwritten. Assert the overwrite parameter if you want overwrite it." % (dstFilename)
879
if title == None: title = self.title
880
if title == None: title = "" # If still None, then set to empty string
881
if filters == None: filters = self.filters
882
dstFileh = openFile(dstFilename, mode="w", title=title)
883
# Copy the user attributes of the root group
884
self.root._v_attrs._f_copy(dstFileh.root)
885
# Copy all the hierarchy
886
ngroups, nleaves, nbytes = \
887
self.root._f_copyChildren(dstFileh.root, recursive=1,
889
copyuserattrs=copyuserattrs)
890
# Finally, close the file
892
return (ngroups, nleaves, nbytes)
894
def listNodes(self, where, classname = ""):
896
"""Returns a list with all the object nodes (Group or Leaf)
897
hanging from "where". The list is alphanumerically sorted by
898
node name. "where" can be a path string or Group instance. If
899
a "classname" parameter is supplied, only instances of this
900
class (or subclasses of it) are returned. The only supported
901
classes in "classname" are 'Group', 'Leaf', 'Table', 'Array',
902
'EArray', 'VLArray' and 'UnImplemented'."""
904
group = self.getNode(where, classname = 'Group')
906
return group._f_listNodes(classname)
910
def __iter__(self, where="/", classname=""):
1124
dstObject = self.getNode(dstnode)
1125
srcObject._v_attrs._f_copy(dstObject)
1128
def copyChildren(self, srcgroup, dstgroup,
1129
overwrite=False, recursive=False, **kwargs):
1131
Copy the children of a group into another group.
1133
This method copies the nodes hanging from the source group
1134
`srcgroup` into the destination group `dstgroup`. Existing
1135
destination nodes can be replaced by asserting the `overwrite`
1136
argument. If the `recursive` argument is true, all descendant
1137
nodes of `srcnode` are recursively copied.
1139
`kwargs` takes keyword arguments used to customize the copying
1140
process. See the documentation of `Group._f_copyChildren()` for
1141
a description of those arguments.
1144
srcGroup = self.getNode(srcgroup) # Does the source node exist?
1145
self._checkGroup(srcGroup) # Is it a group?
1147
srcGroup._f_copyChildren(dstgroup, overwrite, recursive, **kwargs)
1150
def copyFile(self, dstfilename, overwrite=False, **kwargs):
1152
Copy the contents of this file to `dstfilename`.
1154
`dstfilename` must be a path string indicating the name of the
1155
destination file. If it already exists, the copy will fail with
1156
an ``IOError``, unless the `overwrite` argument is true, in
1157
which case the destination file will be overwritten in place.
1159
Additional keyword arguments may be passed to customize the
1160
copying process. For instance, title and filters may be
1161
changed, user attributes may be or may not be copied, data may
1162
be subsampled, stats may be collected, etc. Arguments unknown
1163
to nodes are simply ignored. Check the documentation for
1164
copying operations of nodes to see which options they support.
1166
Copying a file usually has the beneficial side effect of
1167
creating a more compact and cleaner version of the original
1173
# Compute default arguments.
1174
filters = kwargs.get('filters', self.filters)
1175
copyuserattrs = kwargs.get('copyuserattrs', False)
1176
# These are *not* passed on.
1177
title = kwargs.pop('title', self.title)
1179
if os.path.isfile(dstfilename) and not overwrite:
1181
file ``%s`` already exists; \
1182
you may want to use the ``overwrite`` argument""" % dstfilename)
1184
# Create destination file, overwriting it.
1185
dstFileh = openFile(
1186
dstfilename, mode="w", title=title, filters=filters)
1189
# Maybe copy the user attributes of the root group.
1191
self.root._v_attrs._f_copy(dstFileh.root)
1193
# Copy the rest of the hierarchy.
1194
self.root._f_copyChildren(dstFileh.root, recursive=True, **kwargs)
1199
def listNodes(self, where, classname=None):
1201
Return a list with children nodes hanging from `where`.
1203
The `where` argument works as in `getNode()`, referencing the
1204
node to be acted upon. The other arguments work as in
1205
`Group._f_listNodes()`.
1208
group = self.getNode(where) # Does the parent exist?
1209
self._checkGroup(group) # Is it a group?
1211
return group._f_listNodes(classname)
1214
def iterNodes(self, where, classname=None):
1216
Return an iterator yielding children nodes hanging from `where`.
1218
The `where` argument works as in `getNode()`, referencing the
1219
node to be acted upon. The other arguments work as in
1220
`Group._f_listNodes()`.
1222
This is an iterator version of File.listNodes()
1225
group = self.getNode(where) # Does the parent exist?
1226
self._checkGroup(group) # Is it a group?
1228
return group._f_iterNodes(classname)
1231
def __contains__(self, path):
1233
Is there a node with that `path`?
1235
Returns ``True`` if the file has a node with the given `path` (a
1236
string), ``False`` otherwise.
1241
except NoSuchNodeError:
911
1248
"""Iterate over the nodes in the object tree."""
913
return self.walkNodes(where, classname)
915
def walkNodes(self, where="/", classname=""):
1250
return self.walkNodes('/')
1253
def walkNodes(self, where="/", classname=None):
916
1254
"""Iterate over the nodes in the object tree.
917
1255
If "where" supplied, the iteration starts from this group.
918
1256
If "classname" is supplied, only instances of this class are
922
if classname == "Group":
1259
This version iterates over the leaves in the same group in order
1260
to avoid having a list referencing to them and thus, preventing
1261
the LRU cache to remove them after their use.
1264
class_ = getClassByName(classname)
1266
if class_ is Group: # only groups
923
1267
for group in self.walkGroups(where):
925
elif classname in [None, ""]:
926
yield self.getNode(where, "")
927
for group in self.walkGroups(where):
928
for leaf in self.listNodes(group, ""):
931
for group in self.walkGroups(where):
932
for leaf in self.listNodes(group, classname):
1269
elif class_ is Node: # all nodes
1270
yield self.getNode(where)
1271
for group in self.walkGroups(where):
1272
for leaf in self.iterNodes(group):
1274
else: # only nodes of the named type
1275
for group in self.walkGroups(where):
1276
for leaf in self.iterNodes(group, classname):
935
1280
def walkGroups(self, where = "/"):
936
1281
"""Returns the list of Groups (not Leaves) hanging from "where".
941
1286
groups returned includes "where" (or the root object) as well.
945
group = self.getNode(where, classname = 'Group')
1290
group = self.getNode(where) # Does the parent exist?
1291
self._checkGroup(group) # Is it a group?
946
1292
return group._f_walkGroups()
1295
def _checkOpen(self):
1297
Check the state of the file.
1299
If the file is closed, a `ClosedFileError` is raised.
1302
raise ClosedFileError("the file object is closed")
1305
def _isWritable(self):
1306
"""Is this file writable?"""
1307
return self.mode in ('w', 'a', 'r+')
1310
def _checkWritable(self):
1311
"""Check whether the file is writable.
1313
If the file is not writable, a `FileModeError` is raised.
1315
if not self._isWritable():
1316
raise FileModeError("the file is not writable")
1319
def _checkGroup(self, node):
1320
# `node` must already be a node.
1321
if not isinstance(node, Group):
1322
raise TypeError("node ``%s`` is not a group" % (node._v_pathname,))
1325
# <Undo/Redo support>
1327
def isUndoEnabled(self):
1329
Is the Undo/Redo mechanism enabled?
1331
Returns ``True`` if the Undo/Redo mechanism has been enabled for
1332
this file, ``False`` otherwise. Please note that this mechanism
1333
is persistent, so a newly opened PyTables file may already have
1338
return self._undoEnabled
1341
def _checkUndoEnabled(self):
1342
if not self._undoEnabled:
1343
raise UndoRedoError("Undo/Redo feature is currently disabled!")
1346
def _createTransactionGroup(self):
1347
tgroup = TransactionGroupG(
1348
self.root, _transGroupName,
1349
"Transaction information container", new=True)
1350
# The format of the transaction container.
1351
tgroup._v_attrs._g__setattr('FORMATVERSION', _transVersion)
1355
def _createTransaction(self, troot, tid):
1356
return TransactionG(
1357
troot, _transName % tid,
1358
"Transaction number %d" % tid, new=True)
1361
def _createMark(self, trans, mid):
1363
trans, _markName % mid,
1364
"Mark number %d" % mid, new=True)
1367
def enableUndo(self, filters=Filters(complevel=1)):
1369
Enable the Undo/Redo mechanism.
1371
This operation prepares the database for undoing and redoing
1372
modifications in the node hierarchy. This allows `mark()`,
1373
`undo()`, `redo()` and other methods to be called.
1375
The `filters` argument, when specified, must be an instance of
1376
class `Filters` and is meant for setting the compression values
1377
for the action log. The default is having compression enabled,
1378
as the gains in terms of space can be considerable. You may
1379
want to disable compression if you want maximum speed for
1380
Undo/Redo operations.
1382
Calling `enableUndo()` when the Undo/Redo mechanism is already
1383
enabled raises an `UndoRedoError`.
1386
class ActionLog(IsDescription):
1387
opcode = UInt8Col(pos=0)
1388
arg1 = StringCol(MAX_UNDO_PATH_LENGTH, pos=1, dflt="")
1389
arg2 = StringCol(MAX_UNDO_PATH_LENGTH, pos=2, dflt="")
1393
# Enabling several times is not allowed to avoid the user having
1394
# the illusion that a new implicit mark has been created
1395
# when calling enableUndo for the second time.
1397
if self.isUndoEnabled():
1398
raise UndoRedoError, "Undo/Redo feature is already enabled!"
1401
self._seqmarkers = []
1403
self._curtransaction = 0
1404
self._curmark = -1 # No marks yet
1406
# Get the Group for keeping user actions
1408
tgroup = self.getNode(_transGroupPath)
1410
# The file is going to be changed.
1411
self._checkWritable()
1413
# A transaction log group does not exist. Create it
1414
tgroup = self._createTransactionGroup()
1416
# Create a transaction.
1417
self._trans = self._createTransaction(
1418
tgroup, self._curtransaction)
1420
# Create an action log
1421
self._actionlog = Table(
1422
tgroup, _actionLogName, ActionLog, "Action log",
1423
filters=filters, log=False)
1425
# Create an implicit mark
1426
#self._actionlog.append([(_opToCode["MARK"], str(0), '')])
1427
# Use '\x00' to represent a NULL string. This is a bug
1428
# in numarray and should be reported.
1429
# F. Altet 2005-09-21
1430
self._actionlog.append([(_opToCode["MARK"], str(0), '\x00')])
1432
self._seqmarkers.append(0) # current action is 0
1434
# Create a group for mark 0
1435
self._createMark(self._trans, 0)
1436
# Initialize the marker pointer
1437
self._curmark = self._nmarks - 1
1438
# Initialize the action pointer
1439
self._curaction = self._actionlog.nrows - 1
1441
# The group seems to exist already
1442
# Get the default transaction
1443
self._trans = tgroup._f_getChild(
1444
_transName % self._curtransaction)
1445
# Open the action log and go to the end of it
1446
self._actionlog = tgroup.actionlog
1447
for row in self._actionlog:
1448
if row["opcode"] == _opToCode["MARK"]:
1450
self._markers[name] = self._nmarks
1451
self._seqmarkers.append(row.nrow)
1453
# Get the current mark and current action
1454
self._curmark = self._actionlog.attrs.CURMARK
1455
self._curaction = self._actionlog.attrs.CURACTION
1457
# The Undo/Redo mechanism has been enabled.
1458
self._undoEnabled = True
1461
def disableUndo(self):
1463
Disable the Undo/Redo mechanism.
1465
Disabling the Undo/Redo mechanism leaves the database in the
1466
current state and forgets past and future database states. This
1467
makes `mark()`, `undo()`, `redo()` and other methods fail with
1470
Calling `disableUndo()` when the Undo/Redo mechanism is already
1471
disabled raises an `UndoRedoError`.
1476
if not self.isUndoEnabled():
1477
raise UndoRedoError, "Undo/Redo feature is already disabled!"
1479
# The file is going to be changed.
1480
self._checkWritable()
1483
del self._seqmarkers
1486
del self._curtransaction
1489
# Recursively delete the transaction group
1490
tnode = self.getNode(_transGroupPath)
1491
tnode._g_remove(recursive=1)
1493
# The Undo/Redo mechanism has been disabled.
1494
self._undoEnabled = False
1497
def mark(self, name=None):
1499
Mark the state of the database.
1501
Creates a mark for the current state of the database. A unique
1502
(and immutable) identifier for the mark is returned. An
1503
optional `name` (a string) can be assigned to the mark. Both
1504
the identifier of a mark and its name can be used in `undo()`
1505
and `redo()` operations. When the `name` has already been used
1506
for another mark, an `UndoRedoError` is raised.
1508
This method can only be called when the Undo/Redo mechanism has
1509
been enabled. Otherwise, an `UndoRedoError` is raised.
1513
self._checkUndoEnabled()
1518
if not isinstance(name, str):
1520
"Only strings are allowed as mark names. You passed object: '%s'" % name
1521
if name in self._markers:
1522
raise UndoRedoError, \
1523
"Name '%s' is already used as a marker name. Try another one." % name
1525
# The file is going to be changed.
1526
self._checkWritable()
1528
self._markers[name] = self._curmark + 1
1530
# Create an explicit mark
1531
# Insert the mark in the action log
1532
self._log("MARK", str(self._curmark+1), name)
1534
self._nmarks = self._curmark + 1
1535
self._seqmarkers.append(self._curaction)
1536
# Create a group for the current mark
1537
self._createMark(self._trans, self._curmark)
1538
return self._curmark
1541
def _log(self, action, *args):
1545
The `action` must be an all-uppercase string identifying it.
1546
Arguments must also be strings.
1548
This method should be called once the action has been completed.
1550
This method can only be called when the Undo/Redo mechanism has
1551
been enabled. Otherwise, an `UndoRedoError` is raised.
1554
assert self.isUndoEnabled()
1556
# Check whether we are at the end of the action log or not
1557
if self._curaction <> self._actionlog.nrows - 1:
1558
# We are not, so delete the trailing actions
1559
self._actionlog.removeRows(self._curaction + 1,
1560
self._actionlog.nrows)
1561
# Reset the current marker group
1562
mnode = self.getNode(_markPath % (self._curtransaction,
1565
# Delete the marker groups with backup objects
1566
for mark in xrange(self._curmark+1, self._nmarks):
1567
mnode = self.getNode(_markPath % (self._curtransaction, mark))
1568
mnode._g_remove(recursive=1)
1569
# Update the new number of marks
1570
self._nmarks = self._curmark+1
1571
self._seqmarkers = self._seqmarkers[:self._nmarks]
1573
if action not in _opToCode: #INTERNAL
1574
raise UndoRedoError, \
1575
"Action ``%s`` not in ``_opToCode`` dictionary: %r" % \
1578
arg1 = ""; arg2 = ""
1581
elif len(args) <= 2:
1585
raise UndoRedoError, \
1586
"Too many parameters for action log: %r", args
1587
if (len(arg1) > MAX_UNDO_PATH_LENGTH
1588
or len(arg2) > MAX_UNDO_PATH_LENGTH): #INTERNAL
1589
raise UndoRedoError, \
1590
"Parameter arg1 or arg2 is too long: (%r, %r)" % \
1592
#print "Logging-->", (action, arg1, arg2)
1593
self._actionlog.append([(_opToCode[action], arg1, arg2)])
1594
self._curaction += 1
1597
def _getMarkID(self, mark):
1598
"Get an integer markid from a mark sequence number or name"
1600
if isinstance(mark, int):
1602
elif isinstance(mark, str):
1603
if mark not in self._markers:
1604
lmarkers = self._markers.keys()
1606
raise UndoRedoError, \
1607
"The mark that you have specified has not been found in the internal marker list: %r" % lmarkers
1608
markid = self._markers[mark]
1611
"Parameter mark can only be an integer or a string, and you passed a type <%s>" % type(mark)
1612
#print "markid, self._nmarks:", markid, self._nmarks
1616
def _getFinalAction(self, markid):
1617
"Get the action to go. It does not touch the self private attributes"
1619
if markid > self._nmarks - 1:
1620
# The required mark is beyond the end of the action log
1621
# The final action is the last row
1622
return self._actionlog.nrows
1624
# The required mark is the first one
1625
# return the first row
1628
return self._seqmarkers[markid]
1631
# This is a workaround for reversing a RecArray until [::-1] works
1632
##@staticmethod # Python >= 2.4
1633
def _reverseRecArray(recarr):
1635
for f in range(recarr._nfields):
1638
for attr in ["_shape", "_strides", "_bytestride",
1639
"_itemsize", "_byteoffset"]:
1640
setattr(v.field(f), attr, getattr(rev, attr))
1642
_reverseRecArray = staticmethod(_reverseRecArray)
1644
def _doundo(self, finalaction, direction):
1645
"Undo/Redo actions up to final action in the specificed direction"
1648
# Change this when reversing RecArrays will work (numarray > 1.2.2)
1649
#actionlog = self._actionlog[finalaction+1:self._curaction+1][::-1]
1650
actionlog = self._reverseRecArray(
1651
self._actionlog[finalaction+1:self._curaction+1])
1653
actionlog = self._actionlog[self._curaction:finalaction]
1655
# Uncomment this for debugging
1656
# print "curaction, finalaction, direction", \
1657
# self._curaction, finalaction, direction
1658
for i in xrange(len(actionlog)):
1659
if actionlog.field('opcode')[i] <> _opToCode["MARK"]:
1660
# undo/redo the action
1662
# Uncomment this for debugging
1663
# print "redo-->", \
1664
# _codeToOp[actionlog.field('opcode')[i]],\
1665
# actionlog.field('arg1')[i],\
1666
# actionlog.field('arg2')[i]
1668
_codeToOp[actionlog.field('opcode')[i]],
1669
actionlog.field('arg1')[i],
1670
actionlog.field('arg2')[i])
1672
# Uncomment this for debugging
1673
# print "undo-->", \
1674
# _codeToOp[actionlog.field('opcode')[i]],\
1675
# actionlog.field('arg1')[i],\
1676
# actionlog.field('arg2')[i]
1678
_codeToOp[actionlog.field('opcode')[i]],
1679
actionlog.field('arg1')[i],
1680
actionlog.field('arg2')[i])
1683
self._curmark = int(actionlog.field('arg1')[i])
1685
self._curmark = int(actionlog.field('arg1')[i]) - 1
1686
# Protection against negative marks
1687
if self._curmark < 0:
1689
self._curaction += direction
1692
def undo(self, mark=None):
1694
Go to a past state of the database.
1696
Returns the database to the state associated with the specified
1697
`mark`. Both the identifier of a mark and its name can be used.
1698
If the `mark` is omitted, the last created mark is used. If
1699
there are no past marks, or the specified `mark` is not older
1700
than the current one, an `UndoRedoError` is raised.
1702
This method can only be called when the Undo/Redo mechanism has
1703
been enabled. Otherwise, an `UndoRedoError` is raised.
1707
self._checkUndoEnabled()
1709
# print "(pre)UNDO: (curaction, curmark) = (%s,%s)" % \
1710
# (self._curaction, self._curmark)
1712
markid = self._curmark
1713
# Correction if we are settled on top of a mark
1714
opcode = self._actionlog.cols.opcode
1715
if opcode[self._curaction] == _opToCode["MARK"]:
1718
# Get the mark ID number
1719
markid = self._getMarkID(mark)
1720
# Get the final action ID to go
1721
finalaction = self._getFinalAction(markid)
1722
if finalaction > self._curaction:
1723
raise UndoRedoError("""\
1724
Mark ``%s`` is newer than the current mark. Use `redo()` or `goto()` instead."""
1727
# The file is going to be changed.
1728
self._checkWritable()
1730
# Try to reach this mark by unwinding actions in the log
1731
self._doundo(finalaction-1, -1)
1732
if self._curaction < self._actionlog.nrows-1:
1733
self._curaction += 1
1734
self._curmark = int(self._actionlog.cols.arg1[self._curaction])
1735
# print "(post)UNDO: (curaction, curmark) = (%s,%s)" % \
1736
# (self._curaction, self._curmark)
1739
def redo(self, mark=None):
1741
Go to a future state of the database.
1743
Returns the database to the state associated with the specified
1744
`mark`. Both the identifier of a mark and its name can be used.
1745
If the `mark` is omitted, the next created mark is used. If
1746
there are no future marks, or the specified `mark` is not newer
1747
than the current one, an `UndoRedoError` is raised.
1749
This method can only be called when the Undo/Redo mechanism has
1750
been enabled. Otherwise, an `UndoRedoError` is raised.
1754
self._checkUndoEnabled()
1756
# print "(pre)REDO: (curaction, curmark) = (%s, %s)" % \
1757
# (self._curaction, self._curmark)
1758
if self._curaction >= self._actionlog.nrows - 1:
1759
# We are at the end of log, so no action
1763
mark = self._curmark + 1
1765
mark = self._nmarks # Go beyond the mark bounds up to the end
1766
# Get the mark ID number
1767
markid = self._getMarkID(mark)
1768
finalaction = self._getFinalAction(markid)
1769
if finalaction < self._curaction + 1:
1770
raise UndoRedoError("""\
1771
Mark ``%s`` is older than the current mark. Use `redo()` or `goto()` instead."""
1774
# The file is going to be changed.
1775
self._checkWritable()
1777
# Get the final action ID to go
1778
self._curaction += 1
1780
# Try to reach this mark by redoing the actions in the log
1781
self._doundo(finalaction, 1)
1782
# Increment the current mark only if we are not at the end of marks
1783
if self._curmark < self._nmarks - 1:
1785
if self._curaction > self._actionlog.nrows-1:
1786
self._curaction = self._actionlog.nrows-1
1787
# print "(post)REDO: (curaction, curmark) = (%s,%s)" % \
1788
# (self._curaction, self._curmark)
1791
def goto(self, mark):
1793
Go to a specific mark of the database.
1795
Returns the database to the state associated with the specified
1796
`mark`. Both the identifier of a mark and its name can be used.
1798
This method can only be called when the Undo/Redo mechanism has
1799
been enabled. Otherwise, an `UndoRedoError` is raised.
1803
self._checkUndoEnabled()
1805
if mark == -1: # Special case
1806
mark = self._nmarks # Go beyond the mark bounds up to the end
1807
# Get the mark ID number
1808
markid = self._getMarkID(mark)
1809
finalaction = self._getFinalAction(markid)
1810
if finalaction < self._curaction:
1816
def getCurrentMark(self):
1818
Get the identifier of the current mark.
1820
Returns the identifier of the current mark. This can be used to
1821
know the state of a database after an application crash, or to
1822
get the identifier of the initial implicit mark after a call to
1825
This method can only be called when the Undo/Redo mechanism has
1826
been enabled. Otherwise, an `UndoRedoError` is raised.
1830
self._checkUndoEnabled()
1831
return self._curmark
1834
def _shadowName(self):
1836
Compute and return a shadow name.
1838
Computes the current shadow name according to the current
1839
transaction, mark and action. It returns a tuple with the
1840
shadow parent node and the name of the shadow in it.
1843
parent = self.getNode(
1844
_shadowParent % (self._curtransaction, self._curmark))
1845
name = _shadowName % (self._curaction,)
1847
return (parent, name)
1849
# </Undo/Redo support>
949
1852
def flush(self):
950
1853
"""Flush all the objects on all the HDF5 objects tree."""
952
1857
for group in self.walkGroups(self.root):
953
1858
for leaf in self.listNodes(group, classname = 'Leaf'):
956
1861
# Flush the cache to disk
957
1862
self._flushFile(0) # 0 means local scope, 1 global (virtual) scope
960
"""Close all the objects in HDF5 file and close the file."""
1865
def _closeDescendentsOf(self, group):
1866
"""Close all the *loaded* descendent nodes of the given `group`."""
1868
assert isinstance(group, Group)
1870
prefix = group._v_pathname + '/'
1875
[path for path in self._aliveNodes if path.startswith(prefix)])
1878
[path for path in self._deadNodes if path.startswith(prefix)])
1881
def _closeNodes(self, nodePaths, getNode=None):
1883
Close all nodes in the list of `nodePaths`.
1885
This method uses the `getNode` callable object to get the node
1886
object by its path. If `getNode` is not given, `File.getNode()`
1887
is used. ``KeyError`` exceptions on `getNode` invocations are
1892
getNode = self.getNode
1894
for nodePath in nodePaths:
1896
node = getNode(nodePath)
1904
"""Close all the nodes in HDF5 file and close the file."""
962
1907
# If the file is already closed, return immediately
963
1908
if not self.isopen:
966
# Get a list of groups on tree
967
listgroups = self.root._f_getListTree()
968
for group in self.walkGroups(self.root):
969
for leaf in self.listNodes(group, classname = 'Leaf'):
1911
if self._undoEnabled and self._isWritable():
1912
# Save the current mark and current action
1913
self._actionlog.attrs._g__setattr("CURMARK", self._curmark)
1914
self._actionlog.attrs._g__setattr("CURACTION", self._curaction)
1916
# Close all loaded nodes.
1918
# First, close the alive nodes and delete them
1919
# so they are not placed in the limbo again.
1920
# We do not use ``getNode()`` for efficiency.
1921
aliveNodes = self._aliveNodes
1922
# These two steps ensure tables are closed *before* their indices.
1923
self._closeNodes([path for path in aliveNodes.keys()
1924
if '/_i_' not in path], # not indices
1925
lambda path: aliveNodes[path])
1926
self._closeNodes(aliveNodes.keys(), # everything else (i.e. indices)
1927
lambda path: aliveNodes[path])
1928
assert len(aliveNodes) == 0, \
1929
("alive nodes remain after closing alive nodes: %s"
1930
% aliveNodes.keys())
1932
# Next, revive the dead nodes, close and delete them
1933
# so they are not placed in the limbo again.
1934
# We do not use ``getNode()`` for efficiency
1935
# and to avoid accidentally loading ancestor nodes.
1936
deadNodes = self._deadNodes
1937
# These two steps ensure tables are closed *before* their indices.
1938
self._closeNodes([path for path in deadNodes
1939
if '/_i_' not in path], # not indices
1940
lambda path: self._reviveNode(path, useNode=False))
1941
self._closeNodes([path for path in deadNodes],
1942
lambda path: self._reviveNode(path, useNode=False))
1943
assert len(deadNodes) == 0, \
1944
("dead nodes remain after closing dead nodes: %s"
1945
% [path for path in deadNodes])
1947
# No other nodes should have been revived.
1948
assert len(aliveNodes) == 0, \
1949
("alive nodes remain after closing dead nodes: %s"
1950
% aliveNodes.keys())
1952
# When all other nodes have been closed, close the root group.
1953
# This is done at the end because some nodes
1954
# may still need to be loaded during the closing process;
1955
# thus the root node must be open until the very end.
1956
self.root._f_close()
973
1958
# Close the file
974
1959
self._closeFile()
976
1960
# After the objects are disconnected, destroy the
977
1961
# object dictionary using the brute force ;-)
978
1962
# This should help to the garbage collector
979
1963
self.__dict__.clear()
980
1964
# Set the flag to indicate that the file is closed
1966
# Delete the entry in the dictionary of opened files
1967
del _open_files[self]
985
1969
def __str__(self):
986
1970
"""Returns a string representation of the object tree"""
988
1971
# Print all the nodes (Group and Leaf objects) on object tree
989
1972
date = time.asctime(time.localtime(os.stat(self.filename)[8]))
990
1973
astring = self.filename + ' (File) ' + repr(self.title) + '\n'