~ubuntu-branches/ubuntu/natty/pytables/natty-updates

« back to all changes in this revision

Viewing changes to tables/File.py

  • Committer: Bazaar Package Importer
  • Author(s): Alexandre Fayolle
  • Date: 2006-06-28 10:45:03 UTC
  • mfrom: (1.2.1 upstream)
  • mto: This revision was merged to the branch mainline in revision 5.
  • Revision ID: james.westby@ubuntu.com-20060628104503-cc251q5o5j3e2k10
  * Fixed call to pyversions in debian/rules which failed on recent versions 
    of pyversions
  * Fixed clean rule in debian/rules which left the stamp files behind
  * Acknowledge NMU
  * Added Alexandre Fayolle to uploaders

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
########################################################################
1
2
#
2
3
#       License:        BSD
3
4
#       Created:        September 4, 2002
4
 
#       Author:  Francesc Alted - falted@pytables.org
 
5
#       Author:  Francesc Altet - faltet@carabos.com
5
6
#
6
 
#       $Source: /cvsroot/pytables/pytables/tables/File.py,v $
7
 
#       $Id: File.py,v 1.91.2.3 2004/11/16 18:52:21 falted Exp $
 
7
#       $Id: File.py 1509 2006-03-24 09:40:35Z faltet $
8
8
#
9
9
########################################################################
10
10
 
22
22
 
23
23
Functions:
24
24
 
25
 
    copyFile(srcFilename, dstFilename [, title] [, filters]
26
 
             [, copyuserattrs] [, overwrite])
27
 
    openFile(name [, mode = "r"] [, title] [, trMap])
 
25
    copyFile(srcfilename, dstfilename[, overwrite][, **kwargs])
 
26
    openFile(name[, mode][, title][, trMap])
28
27
 
29
28
Misc variables:
30
29
 
34
33
 
35
34
"""
36
35
 
37
 
__version__ = "$Revision: 1.91.2.3 $"
 
36
import warnings
 
37
import time
 
38
import os, os.path
 
39
import weakref
 
40
 
 
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
 
64
 
 
65
 
 
66
 
 
67
__version__ = "$Revision: 1509 $"
 
68
 
 
69
 
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
46
84
 
47
 
from __future__ import generators
48
 
 
49
 
import sys
50
 
import types
51
 
import warnings
52
 
import time
53
 
import os, os.path
54
 
from fnmatch import fnmatch
55
 
import cPickle
56
 
 
57
 
import hdf5Extension
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
66
 
import numarray
 
85
# Dict of opened files (keys are filehandlers and values filenames)
 
86
_open_files = {}
 
87
 
 
88
# Opcodes for do-undo actions
 
89
_opToCode = {"MARK":    0,
 
90
           "CREATE":  1,
 
91
           "REMOVE":  2,
 
92
           "MOVE":    3,
 
93
           "ADDATTR": 4,
 
94
           "DELATTR": 5,
 
95
           }
 
96
 
 
97
_codeToOp = ["MARK", "CREATE", "REMOVE", "MOVE", "ADDATTR", "DELATTR"]
 
98
 
 
99
 
 
100
# Paths and names for hidden nodes related with transactions.
 
101
_transVersion = '1.0'
 
102
 
 
103
_transGroupParent = '/'
 
104
_transGroupName   = '_p_transactions'
 
105
_transGroupPath   = joinPath(_transGroupParent, _transGroupName)
 
106
 
 
107
_actionLogParent = _transGroupPath
 
108
_actionLogName   = 'actionlog'
 
109
_actionLogPath   = joinPath(_actionLogParent, _actionLogName)
 
110
 
 
111
_transParent = _transGroupPath
 
112
_transName   = 't%d'  # %d -> transaction number
 
113
_transPath   = joinPath(_transParent, _transName)
 
114
 
 
115
_markParent = _transPath
 
116
_markName   = 'm%d'  # %d -> mark number
 
117
_markPath   = joinPath(_markParent, _markName)
 
118
 
 
119
_shadowParent = _markPath
 
120
_shadowName   = 'a%d'  # %d -> action number
 
121
_shadowPath   = joinPath(_shadowParent, _shadowName)
 
122
 
 
123
 
67
124
 
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"""),
 
131
                      stacklevel=2)
72
132
        fprops = Filters(complevel=compress, complib=complib)
73
133
    elif filters is None:
74
134
        fprops = None
75
135
    elif isinstance(filters, Filters):
76
136
        fprops = filters
77
137
    else:
78
 
        raise ValueError, \
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)
80
139
    return fprops
81
140
 
82
141
 
83
 
def copyFile(srcFilename = None, dstFilename=None, title=None,
84
 
             filters=None, copyuserattrs=1, overwrite=0):
85
 
    """Copy srcFilename to dstFilename
86
 
 
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".
94
 
 
95
 
    It returns the number of copied groups, leaves and bytes in the
96
 
    form (ngroups, nleaves, nbytes).
97
 
 
98
 
    """
99
 
 
100
 
    # Open the src file
101
 
    srcFileh = openFile(srcFilename, mode="r")
102
 
 
103
 
    # Copy it to the destination
104
 
    ngroups, nleaves, nbytes = srcFileh.copyFile(dstFilename, title=title,
105
 
                                                filters=filters,
106
 
                                                copyuserattrs=copyuserattrs,
107
 
                                                overwrite=overwrite)
108
 
 
109
 
    # Close the source file
110
 
    srcFileh.close()
111
 
    return ngroups, nleaves, nbytes
 
142
def copyFile(srcfilename, dstfilename, overwrite=False, **kwargs):
 
143
    """
 
144
    An easy way of copying one PyTables file to another.
 
145
 
 
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.
 
150
 
 
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.
 
155
    """
 
156
 
 
157
    # Open the source file.
 
158
    srcFileh = openFile(srcfilename, mode="r")
 
159
 
 
160
    try:
 
161
        # Copy it to the destination file.
 
162
        srcFileh.copyFile(dstfilename, overwrite=overwrite, **kwargs)
 
163
    finally:
 
164
        # Close the source file.
 
165
        srcFileh.close()
112
166
 
113
167
 
114
168
def openFile(filename, mode="r", title="", trMap={}, rootUEP="/",
115
 
             filters=None):
 
169
             filters=None, nodeCacheSize=NODE_CACHE_SIZE):
116
170
 
117
 
    """Open an HDF5 file an returns a File object.
 
171
    """Open an HDF5 file and return a File object.
118
172
 
119
173
    Arguments:
120
174
 
122
176
            expansion).
123
177
 
124
178
    mode -- The mode to open the file. It can be one of the following:
125
 
    
 
179
 
126
180
        "r" -- read-only; no data can be modified.
127
 
        
 
181
 
128
182
        "w" -- write; a new file is created (an existing file with the
129
183
               same name is deleted).
130
184
 
145
199
    rootUEP -- The root User Entry Point. It is a group in the file
146
200
            hierarchy which is taken as the starting point to create
147
201
            the object tree. The group can be whatever existing path
148
 
            in the file. If it does not exist, a RuntimeError is
 
202
            in the file. If it does not exist, an HDF5ExtError is
149
203
            issued.
150
204
 
151
205
    filters -- An instance of the Filters class that provides
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.
157
 
            
 
211
 
 
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.
 
217
 
158
218
    """
159
219
 
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)
165
 
    
166
 
# The file extension warning commmented out by people at GL suggestion
167
 
 
168
 
#     if not (fnmatch(path, "*.h5") or
169
 
#             fnmatch(path, "*.hdf") or
170
 
#             fnmatch(path, "*.hdf5")):
171
 
#         warnings.warn( \
172
 
# """filename '%s'should have one of the next file extensions
173
 
#   '.h5', '.hdf' or '.hdf5'. Continuing anyway.""" % path, UserWarning)
174
 
 
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']"""
178
 
 
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):
182
 
            raise IOError, \
183
 
        """'%s' pathname does not exist or is not a regular file""" % path
184
 
        else:
185
 
            if not hdf5Extension.isHDF5(path):
186
 
                raise IOError, \
187
 
        """'%s' does exist but it is not an HDF5 file""" % path
188
 
            
189
 
            elif not hdf5Extension.isPyTablesFile(path):
190
 
                warnings.warn( \
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!.""" % \
192
 
path, UserWarning)
193
 
  
194
 
                isPTFile = 0
195
 
                    
196
 
    elif (mode == "w"):
197
 
        # For 'w' check that if path exists, and if true, delete it!
198
 
        if os.path.isfile(path):
199
 
            # Delete the old file
200
 
            os.remove(path)
201
 
    elif (mode == "a"):            
202
 
        if os.path.isfile(path):
203
 
            if not hdf5Extension.isHDF5(path):
204
 
                raise IOError, \
205
 
        """'%s' does exist but it is not an HDF5 file""" % path
206
 
            
207
 
            elif not hdf5Extension.isPyTablesFile(path):
208
 
                warnings.warn( \
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)
212
 
                isPTFile = 0
213
 
    else:
214
 
        raise IOError, \
215
 
        """arg 2 can only take the new values: "r", "r+", "w" and "a" """
216
 
    
217
 
    # new informs if this file is old or new
218
 
    if (mode == "r" or
219
 
        mode == "r+" or
220
 
        (mode == "a" and os.path.isfile(path)) ):
221
 
        new = 0
222
 
    else:
223
 
        new = 1
224
 
            
 
224
 
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)
 
228
 
 
229
 
 
230
class _AliveNodes(dict):
 
231
 
 
232
    """Stores weak references to nodes in a transparent way."""
 
233
 
 
234
    def __getitem__(self, key):
 
235
        ref = super(_AliveNodes, self).__getitem__(key)
 
236
        return ref()
 
237
 
 
238
    def __setitem__(self, key, value):
 
239
        ref = weakref.ref(value)
 
240
        super(_AliveNodes, self).__setitem__(key, ref)
 
241
 
 
242
 
 
243
 
 
244
class _DeadNodes(tables.lrucache.LRUCache):
 
245
    def pop(self, key):
 
246
        obj = self[key]
 
247
        del self[key]
 
248
        return obj
 
249
 
 
250
 
 
251
 
 
252
class _NodeDict(tables.proxydict.ProxyDict):
 
253
 
 
254
    """
 
255
    A proxy dictionary which is able to delegate access to missing items
 
256
    to the container object (a `File`).
 
257
    """
 
258
 
 
259
    def _getValueFromContainer(self, container, key):
 
260
        return container.getNode(key)
 
261
 
 
262
 
 
263
    def _condition(self, node):
 
264
        """Nodes fulfilling the condition are considered to belong here."""
 
265
        raise NotImplementedError
 
266
 
 
267
 
 
268
    def _warnOnGet(self):
 
269
        warnings.warn("using this mapping object is deprecated; "
 
270
                      "please use ``File.getNode()`` instead",
 
271
                      DeprecationWarning)
 
272
 
 
273
 
 
274
    def __contains__(self, key):
 
275
        self._warnOnGet()
 
276
 
 
277
        # If the key is here there is nothing else to check.
 
278
        if super(_NodeDict, self).__contains__(key):
 
279
            return True
 
280
 
 
281
        # Look if the key is in the container `File`.
 
282
        try:
 
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.
 
289
            return False
 
290
 
 
291
 
 
292
    def __getitem__(self, key):
 
293
        self._warnOnGet()
 
294
        return super(_NodeDict, self).__getitem__(key)
 
295
 
 
296
 
 
297
    # The following operations are quite underperforming
 
298
    # because they need to browse the entire tree.
 
299
    # These objects are deprecated, anyway.
 
300
 
 
301
    def __iter__(self):
 
302
        return self.iterkeys()
 
303
 
 
304
 
 
305
    def iterkeys(self):
 
306
        warnings.warn("using this mapping object is deprecated; "
 
307
                      "please use ``File.walkNodes()`` instead",
 
308
                      DeprecationWarning)
 
309
        for node in self._getContainer().walkNodes('/', self._className):
 
310
            yield node._v_pathname
 
311
        raise StopIteration
 
312
 
 
313
 
 
314
    def __len__(self):
 
315
        nnodes = 0
 
316
        for nodePath in self.iterkeys():
 
317
            nnodes += 1
 
318
        return nnodes
 
319
 
 
320
 
 
321
class _ObjectsDict(_NodeDict):
 
322
 
 
323
    """Maps all visible objects."""
 
324
 
 
325
    _className = None
 
326
 
 
327
    def _condition(self, node):
 
328
        return isVisiblePath(node._v_pathname)
 
329
 
 
330
 
 
331
class _GroupsDict(_NodeDict):
 
332
 
 
333
    """Maps all visible groups."""
 
334
 
 
335
    _className = 'Group'
 
336
 
 
337
    def _condition(self, node):
 
338
        return isVisiblePath(node._v_pathname) and isinstance(node, Group)
 
339
 
 
340
 
 
341
class _LeavesDict(_NodeDict):
 
342
 
 
343
    """Maps all visible leaves."""
 
344
 
 
345
    _className = 'Leaf'
 
346
 
 
347
    def _condition(self, node):
 
348
        return isVisiblePath(node._v_pathname) and isinstance(node, Leaf)
 
349
 
227
350
 
228
351
 
229
352
class File(hdf5Extension.File, object):
230
 
    """Returns an object describing the file in-memory.
231
 
 
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
234
 
    attributes.
235
 
 
236
 
    Methods:
237
 
 
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]
256
 
                     [, overwrite])
257
 
        copyFile(self, dstFilename [, title]
258
 
                 [, filters] [, copyuserattrs] [, overwrite])
259
 
        walkGroups([where])
260
 
        walkNodes([where] [, classname])
261
 
        flush()
262
 
        close()
 
353
 
 
354
    """
 
355
    In-memory representation of a PyTables file.
 
356
 
 
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.
 
363
 
 
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.
 
371
 
 
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
 
379
    are forgotten.
 
380
 
 
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.
 
385
 
 
386
    The Undo/Redo mechanism is persistent between sessions and can only
 
387
    be disabled by calling the `disableUndo()` method.
263
388
 
264
389
    Instance variables:
265
390
 
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
277
 
 
 
391
    filename
 
392
        The name of the opened file.
 
393
    format_version
 
394
        The PyTables version number of this file.
 
395
    isopen
 
396
        True if the underlying file is open, false otherwise.
 
397
    mode
 
398
        The mode in which the file was opened.
 
399
    title
 
400
        The title of the root group in the file.
 
401
    trMap
 
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.
 
407
    rootUEP
 
408
        The UEP (user entry point) group in the file (see the
 
409
        `openFile()` function).
 
410
    filters
 
411
        Default filter properties for the root group (see the `Filters`
 
412
            class).
 
413
    root
 
414
        The *root* of the object tree hierarchy (a `Group` instance).
 
415
    objects
 
416
        A dictionary which maps path names to objects, for every visible
 
417
        node in the tree (deprecated, see note below).
 
418
    groups
 
419
        A dictionary which maps path names to objects, for every visible
 
420
        group in the tree (deprecated, see note below).
 
421
    leaves
 
422
        A dictionary which maps path names to objects, for every visible
 
423
        leaf in the tree (deprecated, see note below).
 
424
 
 
425
    .. note::
 
426
 
 
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.
 
431
 
 
432
 
 
433
    Public methods (file handling):
 
434
 
 
435
    * copyFile(dstfilename[, overwrite][, **kwargs])
 
436
    * flush()
 
437
    * close()
 
438
 
 
439
    Public methods (hierarchy manipulation):
 
440
 
 
441
    * createGroup(where, name[, title][, filters])
 
442
    * createTable(where, name, description[, title][, filters]
 
443
                  [, expectedrows])
 
444
    * createArray(where, name, array[, title])
 
445
    * createEArray(where, name, atom[, title][, filters]
 
446
                   [, expectedrows])
 
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]
 
455
                   [, **kwargs])
 
456
 
 
457
    Public methods (tree traversal):
 
458
 
 
459
    * getNode(where[, name][,classname])
 
460
    * isVisibleNode(path)
 
461
    * listNodes(where[, classname])
 
462
    * walkGroups([where])
 
463
    * walkNodes([where][, classname])
 
464
    * __contains__(path)
 
465
 
 
466
    Public methods (Undo/Redo support):
 
467
 
 
468
    isUndoEnabled()
 
469
        Is the Undo/Redo mechanism enabled?
 
470
    enableUndo([filters])
 
471
        Enable the Undo/Redo mechanism.
 
472
    disableUndo()
 
473
        Disable the Undo/Redo mechanism.
 
474
    mark([name])
 
475
        Mark the state of the database.
 
476
    getCurrentMark()
 
477
        Get the identifier of the current mark.
 
478
    undo([mark])
 
479
        Go to a past state of the database.
 
480
    redo([mark])
 
481
        Go to a future state of the database.
 
482
    goto(mark)
 
483
        Go to a specific mark of the database.
 
484
 
 
485
    Public methods (attribute handling):
 
486
 
 
487
    * getNodeAttr(where, attrname[, name])
 
488
    * setNodeAttr(where, attrname, attrvalue[, name])
 
489
    * delNodeAttr(where, attrname[, name])
 
490
    * copyNodeAttrs(where, dstnode[, name])
278
491
    """
279
492
 
280
 
    def __init__(self, filename, mode="r", title="", new=1, trMap={},
281
 
                 rootUEP="/", isPTFile=1, filters=None):
282
 
        
 
493
    ## <properties>
 
494
 
 
495
    def _gettitle(self):
 
496
        return self.root._v_title
 
497
    def _settitle(self, title):
 
498
        self.root._v_title = title
 
499
    def _deltitle(self):
 
500
        del self.root._v_title
 
501
 
 
502
    title = property(_gettitle, _settitle, _deltitle,
 
503
                     "The title of the root group in the file.")
 
504
 
 
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
 
511
 
 
512
    filters = property(_getfilters, _setfilters, _delfilters,
 
513
                       "Default filter properties for the root group "
 
514
                       "(see the `Filters` class).")
 
515
 
 
516
    ## </properties>
 
517
 
 
518
 
 
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."""
291
531
 
 
532
        global _open_files
 
533
 
292
534
        self.filename = filename
293
 
        #print "Opening the %s HDF5 file ...." % self.filename
294
 
        
295
535
        self.mode = mode
296
 
        self.title = title
297
 
        self._isPTFile = isPTFile
298
 
        
299
 
        # _v_new informs if this file is old or new
300
 
        self._v_new = new
 
536
 
 
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)
 
543
 
 
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)
 
548
        """
 
549
        A dictionary which maps path names to objects, for every visible
 
550
        node in the tree (deprecated).
 
551
        """
 
552
        self.groups = _GroupsDict(self)
 
553
        """
 
554
        A dictionary which maps path names to objects, for every visible
 
555
        group in the tree (deprecated).
 
556
        """
 
557
        self.leaves = _LeavesDict(self)
 
558
        """
 
559
        A dictionary which maps path names to objects, for every visible
 
560
        leaf in the tree (deprecated).
 
561
        """
 
562
 
301
563
        # Assign the trMap and build the reverse translation
302
564
        self.trMap = trMap
303
 
        
 
565
        self._pttoh5 = trMap
 
566
        self._h5topt = {}
 
567
        for (ptname, h5name) in self._pttoh5.iteritems():
 
568
            if h5name in self._h5topt:
 
569
                warnings.warn(
 
570
                    "the translation map has a duplicate HDF5 name %r"
 
571
                    % (h5name,))
 
572
            self._h5topt[h5name] = ptname
 
573
 
 
574
        # For the moment Undo/Redo is not enabled.
 
575
        self._undoEnabled = False
 
576
 
 
577
        new = self._v_new
 
578
 
304
579
        # Filters
305
 
        if self._v_new:
306
 
            if filters is None:
307
 
                # Set the defaults
308
 
                self.filters = Filters()
309
 
            else:
310
 
                self.filters = filters
 
580
        if new and filters is None:
 
581
            # Set the defaults
 
582
            filters = Filters()
 
583
 
 
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.
 
587
        self.isopen = 1
 
588
 
 
589
        # Append the name of the file to the global dict of files opened.
 
590
        _open_files[self] = self.filename
311
591
 
312
592
        # Get the root group from this file
313
 
        self.root = self.__getRootGroup(rootUEP)
314
 
 
315
 
        # Set the flag to indicate that the file has been opened
316
 
        self.isopen = 1
317
 
 
318
 
        return
319
 
 
320
 
    
321
 
    def __getRootGroup(self, rootUEP):
322
 
        
 
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()
 
597
 
 
598
        # Save the PyTables format version for this file.
 
599
        if new:
 
600
            self.format_version = format_version
 
601
            root._v_attrs._g__setattr(
 
602
                'PYTABLES_FORMAT_VERSION', format_version)
 
603
 
 
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.
 
608
            self.enableUndo()
 
609
 
 
610
 
 
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
327
616
        file."""
328
 
          
329
 
        global format_version
330
 
        global compatible_formats
331
 
        
 
617
 
332
618
        self._v_objectID = self._getFileId()
333
 
        self._v_depth = 0
334
619
 
335
620
        if rootUEP in [None, ""]:
336
621
            rootUEP = "/"
337
 
 
338
622
        # Save the User Entry Point in a variable class
339
623
        self.rootUEP=rootUEP
340
624
 
341
 
        rootname = "/"   # Always the name of the root group
342
 
 
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.
348
 
        self.groups = {}
349
 
        self.leaves = {}
350
 
        self.objects = {}
351
 
 
352
 
        rootGroup = Group(self._v_new)
353
 
        
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
362
 
 
363
 
        newattr["_v_name"] = rootname
364
 
        newattr["_v_hdf5name"] = rootUEP
365
 
        newattr["_v_pathname"] = rootname   # Can be rootUEP? I don't think so
366
 
        
367
 
        # Update global path variables for Group
368
 
        self.groups["/"] = rootGroup
369
 
        self.objects["/"] = rootGroup
370
 
        
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()
374
 
 
375
 
        # Attach the AttributeSet attribute to the rootGroup group
376
 
        newattr["_v_attrs"] = AttributeSet(rootGroup)
377
 
 
378
 
        attrsRoot =  rootGroup._v_attrs   # Shortcut
379
 
        if self._v_new:
380
 
            # Set the title
381
 
            newattr["_v_title"] = self.title
382
 
            # Set the filters instance
383
 
            newattr["_v_filters"] = self.filters
384
 
            
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)
391
 
 
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)
400
 
            # Sort them
401
 
            attrsRoot._v_attrnames.sort()
402
 
            attrsRoot._v_attrnamessys.sort()
403
 
 
404
 
        else:
 
625
        new = self._v_new
 
626
 
 
627
        # Get format version *before* getting the object tree
 
628
        if not new:
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')
408
 
            
 
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"
412
 
                          
413
 
            # Get the title for the rootGroup group
414
 
            if hasattr(attrsRoot, "TITLE"):
415
 
                rootGroup.__dict__["_v_title"] = attrsRoot.TITLE
416
 
            else:
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
424
 
            else:
425
 
                self.filters = Filters()
426
 
                      
427
 
            # Get all the groups recursively
428
 
            rootGroup._g_openFile()
429
 
        
430
 
        return rootGroup
431
 
 
432
 
    
433
 
    def _createNode(self, classname, where, name, *args, **kwargs):
434
 
 
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."""
440
 
 
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)
451
 
        else:
452
 
            raise ValueError,\
453
 
            """Parameter 1 can only take 'Group', 'Table', 'Array', EArray or VLArray values."""
454
 
 
455
 
        group = self.getNode(where, classname = 'Group')
456
 
        # Put the object on the tree
457
 
        setattr(group, name, object)
458
 
        return object
459
 
 
460
 
    
461
 
    def createGroup(self, where, name, title = "", filters = None):
 
635
 
 
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)
 
639
 
 
640
 
 
641
    def _ptNameFromH5Name(self, h5Name):
 
642
        """Get the PyTables name matching the given HDF5 name."""
 
643
 
 
644
        ptName = h5Name
 
645
        # This code might seem inefficient but it will be rarely used.
 
646
        for (ptName_, h5Name_) in self.trMap.iteritems():
 
647
            if h5Name_ == h5Name:
 
648
                ptName = ptName_
 
649
                break
 
650
        return ptName
 
651
 
 
652
 
 
653
    def _h5NameFromPTName(self, ptName):
 
654
        """Get the HDF5 name matching the given PyTables name."""
 
655
        return self.trMap.get(ptName, ptName)
 
656
 
 
657
 
 
658
    def createGroup(self, where, name, title="", filters=None):
462
659
        """Create a new Group instance with name "name" in "where" location.
463
660
 
464
661
        Keyword arguments:
473
670
 
474
671
        filters -- An instance of the Filters class that provides
475
672
            information about the desired I/O filters applicable to
476
 
            the leaves that hangs directly from this new group (unless
 
673
            the leaves that hang directly from this new group (unless
477
674
            other filters properties are specified for these leaves,
478
675
            of course). Besides, if you do not specify filter
479
676
            properties for its child groups, they will inherit these
480
677
            ones.
481
 
              
482
678
        """
 
679
        parentNode = self.getNode(where)  # Does the parent node exist?
 
680
        return Group(parentNode, name,
 
681
                     title=title, new=True, filters=filters)
483
682
 
484
 
        group = self.getNode(where, classname = 'Group')
485
 
        setattr(group, name, Group(title, new=1, filters=filters))
486
 
        object = getattr(group, name)
487
 
        return object
488
683
 
489
684
    def createTable(self, where, name, description, title="",
490
685
                    filters=None, expectedrows=10000,
491
686
                    compress=None, complib=None):  # Deprecated
492
 
 
493
687
        """Create a new Table instance with name "name" in "where" location.
494
 
        
 
688
 
495
689
        "where" parameter can be a path string, or another group
496
690
        instance.
497
691
 
503
697
 
504
698
        name -- The name of the new table.
505
699
 
506
 
        description -- A IsDescription subclass or a dictionary where
 
700
        description -- An IsDescription subclass or a dictionary where
507
701
            the keys are the field names, and the values the type
508
702
            definitions. And it can be also a RecArray object (from
509
703
            numarray.records module).
521
715
            management process time and the amount of memory used.
522
716
 
523
717
        """
524
 
 
525
 
        group = self.getNode(where, classname = 'Group')
526
 
        filters = _checkFilters(filters, compress, complib)
527
 
        object = Table(description, title, filters, expectedrows)
528
 
        setattr(group, name, object)
529
 
        return object
530
 
    
531
 
    def createArray(self, where, name, object, title = ""):
532
 
        
 
718
        parentNode = self.getNode(where)  # Does the parent node exist?
 
719
        fprops = _checkFilters(filters, compress, complib)
 
720
        return Table(parentNode, name,
 
721
                     description=description, title=title,
 
722
                     filters=fprops, expectedrows=expectedrows)
 
723
 
 
724
 
 
725
    def createArray(self, where, name, object, title=""):
533
726
        """Create a new instance Array with name "name" in "where" location.
534
727
 
535
728
        Keyword arguments:
541
734
        name -- The name of the new array.
542
735
 
543
736
        object -- The (regular) object to be saved. It can be any of
544
 
            NumArray, CharArray, Numeric, List, Tuple, String, Int of
545
 
            Float types, provided that they are regular (i.e. they are
546
 
            not like [[1,2],2]).
547
 
 
548
 
        title -- Sets a TITLE attribute on the array entity.
549
 
 
550
 
            """
551
 
            
552
 
        group = self.getNode(where, classname = 'Group')
553
 
        Object = Array(object, title)
554
 
        setattr(group, name, Object)
555
 
        return Object
556
 
 
557
 
 
558
 
    def createEArray(self, where, name, atom, title = "",
559
 
                     filters=None, expectedrows = 1000,
 
737
            NumArray, CharArray, NumPy, Numeric or other native Python
 
738
            types, provided that they are regular (i.e. they are not
 
739
            like [[1,2],2]) and homogeneous (i.e. all the elements are
 
740
            of the same type).
 
741
 
 
742
        title -- Sets a TITLE attribute on the array entity.
 
743
 
 
744
        """
 
745
        parentNode = self.getNode(where)  # Does the parent node exist?
 
746
        return Array(parentNode, name,
 
747
                     object=object, title=title)
 
748
 
 
749
 
 
750
    def createCArray(self, where, name, shape, atom, title="",
 
751
                     filters=None, compress=None, complib=None):
 
752
        """Create a new instance CArray with name "name" in "where" location.
 
753
 
 
754
        Keyword arguments:
 
755
 
 
756
        where -- The parent group where the new table will hang
 
757
            from. "where" parameter can be a path string (for example
 
758
            "/level1/leaf5"), or Group instance.
 
759
 
 
760
        name -- The name of the new array.
 
761
 
 
762
        shape -- The shape of the new array.
 
763
 
 
764
        atom -- An Atom instance representing the shape, type and
 
765
            flavor of the chunks to be saved.
 
766
 
 
767
        title -- Sets a TITLE attribute on the array entity.
 
768
 
 
769
        filters -- An instance of the Filters class that provides
 
770
            information about the desired I/O filters to be applied
 
771
            during the life of this object.
 
772
        """
 
773
        parentNode = self.getNode(where)  # Does the parent node exist?
 
774
        fprops = _checkFilters(filters, compress, complib)
 
775
        return CArray(parentNode, name,
 
776
                      shape=shape, atom=atom, title=title, filters=fprops)
 
777
 
 
778
 
 
779
    def createEArray(self, where, name, atom, title="",
 
780
                     filters=None, expectedrows=1000,
560
781
                     compress=None, complib=None):
561
 
        
562
782
        """Create a new instance EArray with name "name" in "where" location.
563
783
 
564
784
        Keyword arguments:
588
808
            this will optimize the HDF5 B-Tree creation and management
589
809
            process time and the amount of memory used.
590
810
 
591
 
            """
592
 
 
593
 
        group = self.getNode(where, classname = 'Group')
594
 
        filters = _checkFilters(filters, compress, complib)
595
 
        Object = EArray(atom, title, filters, expectedrows)
596
 
        setattr(group, name, Object)
597
 
        return Object
598
 
 
599
 
    def createVLArray(self, where, name, atom=None, title="",
 
811
        """
 
812
        parentNode = self.getNode(where)  # Does the parent node exist?
 
813
        fprops = _checkFilters(filters, compress, complib)
 
814
        return EArray(parentNode, name,
 
815
                      atom=atom, title=title, filters=fprops,
 
816
                      expectedrows=expectedrows)
 
817
 
 
818
 
 
819
    def createVLArray(self, where, name, atom, title="",
600
820
                      filters=None, expectedsizeinMB=1.0,
601
821
                      compress=None, complib=None):
602
 
        
603
822
        """Create a new instance VLArray with name "name" in "where" location.
604
823
 
605
824
        Keyword arguments:
626
845
            optimize the HDF5 B-Tree creation and management process
627
846
            time and the amount of memory used.
628
847
 
629
 
            """
630
 
 
631
 
        group = self.getNode(where, classname = 'Group')
632
 
        if atom == None:
633
 
                raise ValueError, \
634
 
                      "please, expecify an atom argument."
635
 
        filters = _checkFilters(filters, compress, complib)
636
 
        Object = VLArray(atom, title, filters, expectedsizeinMB)
637
 
        setattr(group, name, Object)
638
 
        return Object
639
 
 
640
 
 
641
 
    def getNode(self, where, name = "", classname = ""):
642
 
        
643
 
        """Returns the object node "name" under "where" location.
644
 
 
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'."""
653
 
 
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] == "/":
659
 
                where = where[:-1]
660
 
            # This is a string pathname. Get the object ...
661
 
            if name:
662
 
                if where == "/":
663
 
                    strObject = "/" + name
664
 
                else:
665
 
                    strObject = where + "/" + name
666
 
            else:
667
 
                strObject = where
668
 
            # Get the object pointed by strObject path
669
 
            if strObject in self.objects:
670
 
                object = self.objects[strObject]
671
 
            else:
672
 
                # We didn't find the pathname in the object tree.
673
 
                # This should be signaled as an error!.
674
 
                raise LookupError, \
675
 
                      "\"%s\" pathname not found in file: '%s'." % \
676
 
                      (strObject, self.filename)
677
 
                      
678
 
        elif isinstance(where, Group):
679
 
            if name:
680
 
                object = getattr(where, name)
681
 
            else:
682
 
                object = where
683
 
                
684
 
        elif isinstance(where, Leaf):
685
 
            
686
 
            if name:
687
 
                raise LookupError, \
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)
690
 
 
691
 
            else:
692
 
                object = where
693
 
                
694
 
        else:
695
 
            raise TypeError, "Wrong 'where' parameter type (%s)." % \
696
 
                  (type(where))
697
 
            
698
 
        # Finally, check if this object is a classname instance
 
848
        """
 
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)
 
854
 
 
855
 
 
856
    def _getNode(self, nodePath):
 
857
        # The root node is always at hand.
 
858
        if nodePath == '/':
 
859
            return self.root
 
860
 
 
861
        aliveNodes = self._aliveNodes
 
862
        deadNodes = self._deadNodes
 
863
 
 
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
 
873
                break
 
874
            if parentPath in deadNodes:
 
875
                # The parent node is in memory but dead, so revive it.
 
876
                parentNode = self._reviveNode(parentPath)
 
877
                break
 
878
            # Go up one level to try again.
 
879
            (parentPath, nodeName) = splitPath(parentPath)
 
880
            pathTail.insert(0, nodeName)
 
881
        else:
 
882
            # We hit the root node and no parent was in memory.
 
883
            parentNode = self.root
 
884
 
 
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)
 
899
            parentNode = node
 
900
 
 
901
        return node
 
902
 
 
903
 
 
904
    def getNode(self, where, name=None, classname=None):
 
905
        """
 
906
        Get the node under `where` with the given `name`.
 
907
 
 
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.
 
910
 
 
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.
 
915
 
 
916
        In both cases, if the node to be returned does not exist, a
 
917
        `NoSuchNodeError` is raised.  Please note thet hidden nodes are
 
918
        also considered.
 
919
 
 
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.
 
923
        """
 
924
 
 
925
        self._checkOpen()
 
926
 
 
927
        # For compatibility with old default arguments.
 
928
        if name == '':
 
929
            name = None
 
930
 
 
931
        # Get the parent path (and maybe the node itself).
 
932
        if isinstance(where, Node):
 
933
            node = where
 
934
            node._g_checkOpen()  # the node object must be open
 
935
            nodePath = where._v_pathname
 
936
        elif isinstance(where, basestring):  # Pyhton >= 2.3
 
937
            node = None
 
938
            nodePath = where
 
939
        else:
 
940
            raise TypeError(
 
941
                "``where`` is not a string nor a node: %r" % (where,))
 
942
 
 
943
        # Get the name of the child node.
 
944
        if name is not None:
 
945
            node = None
 
946
            nodePath = joinPath(nodePath, name)
 
947
 
 
948
        assert node is None or node._v_pathname == nodePath
 
949
 
 
950
        # Now we have the definitive node path, let us try to get the node.
 
951
        if node is None:
 
952
            node = self._getNode(nodePath)
 
953
 
 
954
        # Finally, check whether the desired node is an instance
 
955
        # of the expected class.
699
956
        if classname:
700
 
            classobj = eval(classname)
701
 
            if isinstance(object, classobj):
702
 
                return object
703
 
            else:
704
 
                #warnings.warn( \
705
 
                # This warning has been changed to a LookupError because
706
 
                # I think it is more consistent
707
 
                raise LookupError, \
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__)
710
 
                #, UserWarning)
711
 
                #return -1
712
 
        return object
713
 
 
714
 
    def renameNode(self, where, newname, name = ""):
715
 
        """Rename the object node "name" under "where" location.
716
 
 
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.
722
 
        
723
 
        """
724
 
 
725
 
        # Get the node to be renamed
726
 
        object = self.getNode(where, name=name)
727
 
        if isinstance(object, Group):
728
 
            object._f_rename(newname)
729
 
        else:
730
 
            object.rename(newname)
731
 
        
732
 
    def removeNode(self, where, name = "", recursive = 0):
733
 
        """Removes the object node "name" under "where" location.
734
 
 
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.
742
 
 
743
 
        """
744
 
 
745
 
        # Get the node to be removed
746
 
        object = self.getNode(where, name=name)
747
 
        if isinstance(object, Group):
748
 
            object._f_remove(recursive)
749
 
        else:
750
 
            object.remove()            
751
 
        
752
 
    def getAttrNode(self, where, attrname, name = ""):
753
 
        """Returns the attribute "attrname" of node "where"."name".
754
 
 
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.
760
 
        
761
 
        """
762
 
 
763
 
        object = self.getNode(where, name=name)
764
 
        if isinstance(object, Group):
765
 
            return object._f_getAttr(attrname)
766
 
        else:
767
 
            return object.getAttr(attrname)
768
 
            
769
 
    def setAttrNode(self, where, attrname, attrvalue, name=""):
770
 
        """Set the attribute "attrname" of node "where"."name".
771
 
 
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"
777
 
        its value.
778
 
        
779
 
        """
780
 
 
781
 
        object = self.getNode(where, name=name)
782
 
        if isinstance(object, Group):
783
 
            object._f_setAttr(attrname, attrvalue)
784
 
        else:
785
 
            object.setAttr(attrname, attrvalue)
786
 
        
787
 
    def delAttrNode(self, where, attrname, name = ""):
788
 
        """Delete the attribute "attrname" of node "where"."name".
789
 
 
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.
795
 
        
796
 
        """
797
 
 
798
 
        object = self.getNode(where, name=name)
799
 
        if isinstance(object, Group):
800
 
            return object._f_delAttr(attrname)
801
 
        else:
802
 
            return object.delAttr(attrname)
803
 
            
804
 
    def copyAttrs(self, where, name="", dstNode=None):
805
 
        """Copy the attributes from node "where"."name" to "dstNode".
806
 
 
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
812
 
        or a Node object.
813
 
        
814
 
        """
815
 
 
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))
 
967
 
 
968
        return node
 
969
 
 
970
 
 
971
    def isVisibleNode(self, path):
 
972
        """
 
973
        Is the node under `path` visible?
 
974
 
 
975
        If the node does not exist, a ``NoSuchNodeError`` is raised.
 
976
        """
 
977
 
 
978
        # ``util.isVisiblePath()`` is still recommended for internal use.
 
979
        return self.getNode(path)._f_isVisible()
 
980
 
 
981
 
 
982
    def renameNode(self, where, newname, name=None):
 
983
        """
 
984
        Rename the given node in place.
 
985
 
 
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()`.
 
989
        """
 
990
        obj = self.getNode(where, name=name)
 
991
        obj._f_rename(newname)
 
992
 
 
993
    def moveNode(self, where, newparent=None, newname=None, name=None,
 
994
                 overwrite=False):
 
995
        """
 
996
        Move or rename the given node.
 
997
 
 
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()`.
 
1001
        """
 
1002
        obj = self.getNode(where, name=name)
 
1003
        obj._f_move(newparent, newname, overwrite)
 
1004
 
 
1005
    def copyNode(self, where, newparent=None, newname=None, name=None,
 
1006
                 overwrite=False, recursive=False, **kwargs):
 
1007
        """
 
1008
        Copy the given node and return the new one.
 
1009
 
 
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()`.
 
1013
        """
 
1014
        obj = self.getNode(where, name=name)
 
1015
        return obj._f_copy(newparent, newname, overwrite, recursive, **kwargs)
 
1016
 
 
1017
    def removeNode(self, where, name=None, recursive=False):
 
1018
        """
 
1019
        Remove the given node from the hierarchy.
 
1020
 
 
1021
 
 
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()`.
 
1025
        """
 
1026
        obj = self.getNode(where, name=name)
 
1027
        obj._f_remove(recursive)
 
1028
 
 
1029
 
 
1030
    def getAttrNode(self, where, attrname, name=None):
 
1031
        """
 
1032
        Get a PyTables attribute from the given node.
 
1033
 
 
1034
        This method is deprecated; please use `getNodeAttr()`.
 
1035
        """
 
1036
 
 
1037
        warnings.warn("""\
 
1038
``File.getAttrNode()`` is deprecated; please use ``File.getNodeAttr()``""",
 
1039
                      DeprecationWarning)
 
1040
        return self.getNodeAttr(where, attrname, name)
 
1041
 
 
1042
    def getNodeAttr(self, where, attrname, name=None):
 
1043
        """
 
1044
        Get a PyTables attribute from the given node.
 
1045
 
 
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()`.
 
1049
        """
 
1050
        obj = self.getNode(where, name=name)
 
1051
        return obj._f_getAttr(attrname)
 
1052
 
 
1053
 
 
1054
    def setAttrNode(self, where, attrname, attrvalue, name=None):
 
1055
        """
 
1056
        Set a PyTables attribute for the given node.
 
1057
 
 
1058
        This method is deprecated; please use `setNodeAttr()`.
 
1059
        """
 
1060
 
 
1061
        warnings.warn("""\
 
1062
``File.setAttrNode()`` is deprecated; please use ``File.setNodeAttr()``""",
 
1063
                      DeprecationWarning)
 
1064
        self.setNodeAttr(where, attrname, attrvalue, name)
 
1065
 
 
1066
    def setNodeAttr(self, where, attrname, attrvalue, name=None):
 
1067
        """
 
1068
        Set a PyTables attribute for the given node.
 
1069
 
 
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()`.
 
1073
        """
 
1074
        obj = self.getNode(where, name=name)
 
1075
        obj._f_setAttr(attrname, attrvalue)
 
1076
 
 
1077
 
 
1078
    def delAttrNode(self, where, attrname, name=None):
 
1079
        """
 
1080
        Delete a PyTables attribute from the given node.
 
1081
 
 
1082
        This method is deprecated; please use `delNodeAttr()`.
 
1083
        """
 
1084
 
 
1085
        warnings.warn("""\
 
1086
``File.delAttrNode()`` is deprecated; please use ``File.delNodeAttr()``""",
 
1087
                      DeprecationWarning)
 
1088
        self.delNodeAttr(where, attrname, name)
 
1089
 
 
1090
    def delNodeAttr(self, where, attrname, name=None):
 
1091
        """
 
1092
        Delete a PyTables attribute from the given node.
 
1093
 
 
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()`.
 
1097
        """
 
1098
        obj = self.getNode(where, name=name)
 
1099
        obj._f_delAttr(attrname)
 
1100
 
 
1101
 
 
1102
    def copyAttrs(self, where, dstnode, name=None):
 
1103
        """
 
1104
        Copy attributes from one node to another.
 
1105
 
 
1106
        This method is deprecated; please use `copyNodeAttrs()`.
 
1107
        """
 
1108
 
 
1109
        warnings.warn("""\
 
1110
``File.copyAttrs()`` is deprecated; please use ``File.copyNodeAttrs()``""",
 
1111
                      DeprecationWarning)
 
1112
        self.copyNodeAttrs(where, dstnode, name)
 
1113
 
 
1114
    def copyNodeAttrs(self, where, dstnode, name=None):
 
1115
        """
 
1116
        Copy attributes from one node to another.
 
1117
 
 
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`
 
1121
        instance.
 
1122
        """
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)
821
 
        else:
822
 
            object.attrs._f_copy(dstNode)
823
 
        
824
 
    def copyChildren(self, whereSrc, whereDst, recursive=0, filters=None,
825
 
                   copyuserattrs=1, start=0, stop=None, step=1,
826
 
                   overwrite = 0):
827
 
        """(Recursively) Copy the children of a group into another location
828
 
 
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.
843
 
 
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.
847
 
 
848
 
        """
849
 
 
850
 
        srcGroup = self.getNode(whereSrc, classname="Group")
851
 
        ngroups, nleaves, nbytes = \
852
 
                 srcGroup._f_copyChildren(where=whereDst, recursive=recursive,
853
 
                                        filters=filters,
854
 
                                        copyuserattrs=copyuserattrs,
855
 
                                        start=start, stop=stop, step=step,
856
 
                                        overwrite=overwrite)
857
 
        # return the number of objects copied as well as the nuber of bytes
858
 
        return (ngroups, nleaves, nbytes)
859
 
        
860
 
    def copyFile(self, dstFilename=None, title=None,
861
 
                 filters=None, copyuserattrs=1, overwrite=0):
862
 
        """Copy the contents of this file to "dstFilename".
863
 
 
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.
870
 
 
871
 
        This copy also has the effect of compacting the destination
872
 
        file during the process.
873
 
        
874
 
        """
875
 
 
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)
878
 
 
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,
888
 
                                         filters=filters,
889
 
                                         copyuserattrs=copyuserattrs)
890
 
        # Finally, close the file
891
 
        dstFileh.close()
892
 
        return (ngroups, nleaves, nbytes)
893
 
        
894
 
    def listNodes(self, where, classname = ""):
895
 
        
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'."""
903
 
 
904
 
        group = self.getNode(where, classname = 'Group')
905
 
        if group <> -1:
906
 
            return group._f_listNodes(classname)
907
 
        else:
908
 
            return []
909
 
    
910
 
    def __iter__(self, where="/", classname=""):
 
1124
        dstObject = self.getNode(dstnode)
 
1125
        srcObject._v_attrs._f_copy(dstObject)
 
1126
 
 
1127
 
 
1128
    def copyChildren(self, srcgroup, dstgroup,
 
1129
                     overwrite=False, recursive=False, **kwargs):
 
1130
        """
 
1131
        Copy the children of a group into another group.
 
1132
 
 
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.
 
1138
 
 
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.
 
1142
        """
 
1143
 
 
1144
        srcGroup = self.getNode(srcgroup)  # Does the source node exist?
 
1145
        self._checkGroup(srcGroup)  # Is it a group?
 
1146
 
 
1147
        srcGroup._f_copyChildren(dstgroup, overwrite, recursive, **kwargs)
 
1148
 
 
1149
 
 
1150
    def copyFile(self, dstfilename, overwrite=False, **kwargs):
 
1151
        """
 
1152
        Copy the contents of this file to `dstfilename`.
 
1153
 
 
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.
 
1158
 
 
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.
 
1165
 
 
1166
        Copying a file usually has the beneficial side effect of
 
1167
        creating a more compact and cleaner version of the original
 
1168
        file.
 
1169
        """
 
1170
 
 
1171
        self._checkOpen()
 
1172
 
 
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)
 
1178
 
 
1179
        if os.path.isfile(dstfilename) and not overwrite:
 
1180
            raise IOError("""\
 
1181
file ``%s`` already exists; \
 
1182
you may want to use the ``overwrite`` argument""" % dstfilename)
 
1183
 
 
1184
        # Create destination file, overwriting it.
 
1185
        dstFileh = openFile(
 
1186
            dstfilename, mode="w", title=title, filters=filters)
 
1187
 
 
1188
        try:
 
1189
            # Maybe copy the user attributes of the root group.
 
1190
            if copyuserattrs:
 
1191
                self.root._v_attrs._f_copy(dstFileh.root)
 
1192
 
 
1193
            # Copy the rest of the hierarchy.
 
1194
            self.root._f_copyChildren(dstFileh.root, recursive=True, **kwargs)
 
1195
        finally:
 
1196
            dstFileh.close()
 
1197
 
 
1198
 
 
1199
    def listNodes(self, where, classname=None):
 
1200
        """
 
1201
        Return a list with children nodes hanging from `where`.
 
1202
 
 
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()`.
 
1206
        """
 
1207
 
 
1208
        group = self.getNode(where)  # Does the parent exist?
 
1209
        self._checkGroup(group)  # Is it a group?
 
1210
 
 
1211
        return group._f_listNodes(classname)
 
1212
 
 
1213
 
 
1214
    def iterNodes(self, where, classname=None):
 
1215
        """
 
1216
        Return an iterator yielding children nodes hanging from `where`.
 
1217
 
 
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()`.
 
1221
 
 
1222
        This is an iterator version of File.listNodes()
 
1223
        """
 
1224
 
 
1225
        group = self.getNode(where)  # Does the parent exist?
 
1226
        self._checkGroup(group)  # Is it a group?
 
1227
 
 
1228
        return group._f_iterNodes(classname)
 
1229
 
 
1230
 
 
1231
    def __contains__(self, path):
 
1232
        """
 
1233
        Is there a node with that `path`?
 
1234
 
 
1235
        Returns ``True`` if the file has a node with the given `path` (a
 
1236
        string), ``False`` otherwise.
 
1237
        """
 
1238
 
 
1239
        try:
 
1240
            self.getNode(path)
 
1241
        except NoSuchNodeError:
 
1242
            return False
 
1243
        else:
 
1244
            return True
 
1245
 
 
1246
 
 
1247
    def __iter__(self):
911
1248
        """Iterate over the nodes in the object tree."""
912
1249
 
913
 
        return self.walkNodes(where, classname)
914
 
 
915
 
    def walkNodes(self, where="/", classname=""):
 
1250
        return self.walkNodes('/')
 
1251
 
 
1252
 
 
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
919
1257
        returned.
920
 
        
921
 
        """        
922
 
        if classname == "Group":
 
1258
 
 
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.
 
1262
        """
 
1263
 
 
1264
        class_ = getClassByName(classname)
 
1265
 
 
1266
        if class_ is Group:  # only groups
923
1267
            for group in self.walkGroups(where):
924
1268
                yield group
925
 
        elif classname in [None, ""]:
926
 
            yield self.getNode(where, "")
927
 
            for group in self.walkGroups(where):
928
 
                for leaf in self.listNodes(group, ""):
929
 
                    yield leaf
930
 
        else:
931
 
            for group in self.walkGroups(where):
932
 
                for leaf in self.listNodes(group, classname):
933
 
                    yield leaf
934
 
                
 
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):
 
1273
                    yield leaf
 
1274
        else:  # only nodes of the named type
 
1275
            for group in self.walkGroups(where):
 
1276
                for leaf in self.iterNodes(group, classname):
 
1277
                    yield leaf
 
1278
 
 
1279
 
935
1280
    def walkGroups(self, where = "/"):
936
1281
        """Returns the list of Groups (not Leaves) hanging from "where".
937
1282
 
941
1286
        groups returned includes "where" (or the root object) as well.
942
1287
 
943
1288
        """
944
 
        
945
 
        group = self.getNode(where, classname = 'Group')
 
1289
 
 
1290
        group = self.getNode(where)  # Does the parent exist?
 
1291
        self._checkGroup(group)  # Is it a group?
946
1292
        return group._f_walkGroups()
947
1293
 
948
 
                    
 
1294
 
 
1295
    def _checkOpen(self):
 
1296
        """
 
1297
        Check the state of the file.
 
1298
 
 
1299
        If the file is closed, a `ClosedFileError` is raised.
 
1300
        """
 
1301
        if not self.isopen:
 
1302
            raise ClosedFileError("the file object is closed")
 
1303
 
 
1304
 
 
1305
    def _isWritable(self):
 
1306
        """Is this file writable?"""
 
1307
        return self.mode in ('w', 'a', 'r+')
 
1308
 
 
1309
 
 
1310
    def _checkWritable(self):
 
1311
        """Check whether the file is writable.
 
1312
 
 
1313
        If the file is not writable, a `FileModeError` is raised.
 
1314
        """
 
1315
        if not self._isWritable():
 
1316
            raise FileModeError("the file is not writable")
 
1317
 
 
1318
 
 
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,))
 
1323
 
 
1324
 
 
1325
    # <Undo/Redo support>
 
1326
 
 
1327
    def isUndoEnabled(self):
 
1328
        """
 
1329
        Is the Undo/Redo mechanism enabled?
 
1330
 
 
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
 
1334
        Undo/Redo support.
 
1335
        """
 
1336
 
 
1337
        self._checkOpen()
 
1338
        return self._undoEnabled
 
1339
 
 
1340
 
 
1341
    def _checkUndoEnabled(self):
 
1342
        if not self._undoEnabled:
 
1343
            raise UndoRedoError("Undo/Redo feature is currently disabled!")
 
1344
 
 
1345
 
 
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)
 
1352
        return tgroup
 
1353
 
 
1354
 
 
1355
    def _createTransaction(self, troot, tid):
 
1356
        return TransactionG(
 
1357
            troot, _transName % tid,
 
1358
            "Transaction number %d" % tid, new=True)
 
1359
 
 
1360
 
 
1361
    def _createMark(self, trans, mid):
 
1362
        return MarkG(
 
1363
            trans, _markName % mid,
 
1364
            "Mark number %d" % mid, new=True)
 
1365
 
 
1366
 
 
1367
    def enableUndo(self, filters=Filters(complevel=1)):
 
1368
        """
 
1369
        Enable the Undo/Redo mechanism.
 
1370
 
 
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.
 
1374
 
 
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.
 
1381
 
 
1382
        Calling `enableUndo()` when the Undo/Redo mechanism is already
 
1383
        enabled raises an `UndoRedoError`.
 
1384
        """
 
1385
 
 
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="")
 
1390
 
 
1391
        self._checkOpen()
 
1392
 
 
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.
 
1396
 
 
1397
        if self.isUndoEnabled():
 
1398
            raise UndoRedoError, "Undo/Redo feature is already enabled!"
 
1399
 
 
1400
        self._markers = {}
 
1401
        self._seqmarkers = []
 
1402
        self._nmarks = 0
 
1403
        self._curtransaction = 0
 
1404
        self._curmark = -1  # No marks yet
 
1405
 
 
1406
        # Get the Group for keeping user actions
 
1407
        try:
 
1408
            tgroup = self.getNode(_transGroupPath)
 
1409
        except NodeError:
 
1410
            # The file is going to be changed.
 
1411
            self._checkWritable()
 
1412
 
 
1413
            # A transaction log group does not exist. Create it
 
1414
            tgroup = self._createTransactionGroup()
 
1415
 
 
1416
            # Create a transaction.
 
1417
            self._trans = self._createTransaction(
 
1418
                tgroup, self._curtransaction)
 
1419
 
 
1420
            # Create an action log
 
1421
            self._actionlog = Table(
 
1422
                tgroup, _actionLogName, ActionLog, "Action log",
 
1423
                filters=filters, log=False)
 
1424
 
 
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')])
 
1431
            self._nmarks += 1
 
1432
            self._seqmarkers.append(0) # current action is 0
 
1433
 
 
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
 
1440
        else:
 
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"]:
 
1449
                    name = row["arg2"]
 
1450
                    self._markers[name] = self._nmarks
 
1451
                    self._seqmarkers.append(row.nrow)
 
1452
                    self._nmarks += 1
 
1453
            # Get the current mark and current action
 
1454
            self._curmark = self._actionlog.attrs.CURMARK
 
1455
            self._curaction = self._actionlog.attrs.CURACTION
 
1456
 
 
1457
        # The Undo/Redo mechanism has been enabled.
 
1458
        self._undoEnabled = True
 
1459
 
 
1460
 
 
1461
    def disableUndo(self):
 
1462
        """
 
1463
        Disable the Undo/Redo mechanism.
 
1464
 
 
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
 
1468
        an `UndoRedoError`.
 
1469
 
 
1470
        Calling `disableUndo()` when the Undo/Redo mechanism is already
 
1471
        disabled raises an `UndoRedoError`.
 
1472
        """
 
1473
 
 
1474
        self._checkOpen()
 
1475
 
 
1476
        if not self.isUndoEnabled():
 
1477
            raise UndoRedoError, "Undo/Redo feature is already disabled!"
 
1478
 
 
1479
        # The file is going to be changed.
 
1480
        self._checkWritable()
 
1481
 
 
1482
        del self._markers
 
1483
        del self._seqmarkers
 
1484
        del self._curmark
 
1485
        del self._curaction
 
1486
        del self._curtransaction
 
1487
        del self._nmarks
 
1488
        del self._actionlog
 
1489
        # Recursively delete the transaction group
 
1490
        tnode = self.getNode(_transGroupPath)
 
1491
        tnode._g_remove(recursive=1)
 
1492
 
 
1493
        # The Undo/Redo mechanism has been disabled.
 
1494
        self._undoEnabled = False
 
1495
 
 
1496
 
 
1497
    def mark(self, name=None):
 
1498
        """
 
1499
        Mark the state of the database.
 
1500
 
 
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.
 
1507
 
 
1508
        This method can only be called when the Undo/Redo mechanism has
 
1509
        been enabled.  Otherwise, an `UndoRedoError` is raised.
 
1510
        """
 
1511
 
 
1512
        self._checkOpen()
 
1513
        self._checkUndoEnabled()
 
1514
 
 
1515
        if name is None:
 
1516
            name = ''
 
1517
        else:
 
1518
            if not isinstance(name, str):
 
1519
                raise TypeError, \
 
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
 
1524
 
 
1525
            # The file is going to be changed.
 
1526
            self._checkWritable()
 
1527
 
 
1528
            self._markers[name] = self._curmark + 1
 
1529
 
 
1530
        # Create an explicit mark
 
1531
        # Insert the mark in the action log
 
1532
        self._log("MARK", str(self._curmark+1), name)
 
1533
        self._curmark += 1
 
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
 
1539
 
 
1540
 
 
1541
    def _log(self, action, *args):
 
1542
        """
 
1543
        Log an action.
 
1544
 
 
1545
        The `action` must be an all-uppercase string identifying it.
 
1546
        Arguments must also be strings.
 
1547
 
 
1548
        This method should be called once the action has been completed.
 
1549
 
 
1550
        This method can only be called when the Undo/Redo mechanism has
 
1551
        been enabled.  Otherwise, an `UndoRedoError` is raised.
 
1552
        """
 
1553
 
 
1554
        assert self.isUndoEnabled()
 
1555
 
 
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,
 
1563
                                               self._curmark))
 
1564
            mnode._g_reset()
 
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]
 
1572
 
 
1573
        if action not in _opToCode:  #INTERNAL
 
1574
            raise UndoRedoError, \
 
1575
                  "Action ``%s`` not in ``_opToCode`` dictionary: %r" %  \
 
1576
                  (action, _opToCode)
 
1577
 
 
1578
        arg1 = ""; arg2 = ""
 
1579
        if len(args) <= 1:
 
1580
            arg1 = args[0]
 
1581
        elif len(args) <= 2:
 
1582
            arg1 = args[0]
 
1583
            arg2 = args[1]
 
1584
        else:  #INTERNAL
 
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)" %  \
 
1591
                  (arg1, arg2)
 
1592
        #print "Logging-->", (action, arg1, arg2)
 
1593
        self._actionlog.append([(_opToCode[action], arg1, arg2)])
 
1594
        self._curaction += 1
 
1595
 
 
1596
 
 
1597
    def _getMarkID(self, mark):
 
1598
        "Get an integer markid from a mark sequence number or name"
 
1599
 
 
1600
        if isinstance(mark, int):
 
1601
            markid = mark
 
1602
        elif isinstance(mark, str):
 
1603
            if mark not in self._markers:
 
1604
                lmarkers = self._markers.keys()
 
1605
                lmarkers.sort()
 
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]
 
1609
        else:
 
1610
            raise TypeError, \
 
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
 
1613
        return markid
 
1614
 
 
1615
 
 
1616
    def _getFinalAction(self, markid):
 
1617
        "Get the action to go. It does not touch the self private attributes"
 
1618
 
 
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
 
1623
        elif markid <= 0:
 
1624
            # The required mark is the first one
 
1625
            # return the first row
 
1626
            return 0
 
1627
 
 
1628
        return self._seqmarkers[markid]
 
1629
 
 
1630
 
 
1631
    # This is a workaround for reversing a RecArray until [::-1] works
 
1632
    ##@staticmethod  # Python >= 2.4
 
1633
    def _reverseRecArray(recarr):
 
1634
        v = recarr.view()
 
1635
        for f in range(recarr._nfields):
 
1636
            fow = v.field(f)
 
1637
            rev = fow[::-1]
 
1638
            for attr in ["_shape", "_strides", "_bytestride",
 
1639
                         "_itemsize", "_byteoffset"]:
 
1640
                setattr(v.field(f), attr, getattr(rev, attr))
 
1641
        return v
 
1642
    _reverseRecArray = staticmethod(_reverseRecArray)
 
1643
 
 
1644
    def _doundo(self, finalaction, direction):
 
1645
        "Undo/Redo actions up to final action in the specificed direction"
 
1646
 
 
1647
        if direction < 0:
 
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])
 
1652
        else:
 
1653
            actionlog = self._actionlog[self._curaction:finalaction]
 
1654
 
 
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
 
1661
                if direction > 0:
 
1662
                    # Uncomment this for debugging
 
1663
#                     print "redo-->", \
 
1664
#                           _codeToOp[actionlog.field('opcode')[i]],\
 
1665
#                           actionlog.field('arg1')[i],\
 
1666
#                           actionlog.field('arg2')[i]
 
1667
                    undoredo.redo(self,
 
1668
                                  _codeToOp[actionlog.field('opcode')[i]],
 
1669
                                  actionlog.field('arg1')[i],
 
1670
                                  actionlog.field('arg2')[i])
 
1671
                else:
 
1672
                    # Uncomment this for debugging
 
1673
#                     print "undo-->", \
 
1674
#                           _codeToOp[actionlog.field('opcode')[i]],\
 
1675
#                           actionlog.field('arg1')[i],\
 
1676
#                           actionlog.field('arg2')[i]
 
1677
                    undoredo.undo(self,
 
1678
                                  _codeToOp[actionlog.field('opcode')[i]],
 
1679
                                  actionlog.field('arg1')[i],
 
1680
                                  actionlog.field('arg2')[i])
 
1681
            else:
 
1682
                if direction > 0:
 
1683
                    self._curmark = int(actionlog.field('arg1')[i])
 
1684
                else:
 
1685
                    self._curmark = int(actionlog.field('arg1')[i]) - 1
 
1686
                    # Protection against negative marks
 
1687
                    if self._curmark < 0:
 
1688
                        self._curmark = 0
 
1689
            self._curaction += direction
 
1690
 
 
1691
 
 
1692
    def undo(self, mark=None):
 
1693
        """
 
1694
        Go to a past state of the database.
 
1695
 
 
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.
 
1701
 
 
1702
        This method can only be called when the Undo/Redo mechanism has
 
1703
        been enabled.  Otherwise, an `UndoRedoError` is raised.
 
1704
        """
 
1705
 
 
1706
        self._checkOpen()
 
1707
        self._checkUndoEnabled()
 
1708
 
 
1709
#         print "(pre)UNDO: (curaction, curmark) = (%s,%s)" % \
 
1710
#               (self._curaction, self._curmark)
 
1711
        if mark is None:
 
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"]:
 
1716
                markid -= 1
 
1717
        else:
 
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."""
 
1725
                                % (mark,))
 
1726
 
 
1727
        # The file is going to be changed.
 
1728
        self._checkWritable()
 
1729
 
 
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)
 
1737
 
 
1738
 
 
1739
    def redo(self, mark=None):
 
1740
        """
 
1741
        Go to a future state of the database.
 
1742
 
 
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.
 
1748
 
 
1749
        This method can only be called when the Undo/Redo mechanism has
 
1750
        been enabled.  Otherwise, an `UndoRedoError` is raised.
 
1751
        """
 
1752
 
 
1753
        self._checkOpen()
 
1754
        self._checkUndoEnabled()
 
1755
 
 
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
 
1760
            return
 
1761
 
 
1762
        if mark is None:
 
1763
            mark = self._curmark + 1
 
1764
        elif mark == -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."""
 
1772
                                % (mark,))
 
1773
 
 
1774
        # The file is going to be changed.
 
1775
        self._checkWritable()
 
1776
 
 
1777
        # Get the final action ID to go
 
1778
        self._curaction += 1
 
1779
 
 
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:
 
1784
            self._curmark += 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)
 
1789
 
 
1790
 
 
1791
    def goto(self, mark):
 
1792
        """
 
1793
        Go to a specific mark of the database.
 
1794
 
 
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.
 
1797
 
 
1798
        This method can only be called when the Undo/Redo mechanism has
 
1799
        been enabled.  Otherwise, an `UndoRedoError` is raised.
 
1800
        """
 
1801
 
 
1802
        self._checkOpen()
 
1803
        self._checkUndoEnabled()
 
1804
 
 
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:
 
1811
            self.undo(mark)
 
1812
        else:
 
1813
            self.redo(mark)
 
1814
 
 
1815
 
 
1816
    def getCurrentMark(self):
 
1817
        """
 
1818
        Get the identifier of the current mark.
 
1819
 
 
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
 
1823
        `enableUndo()`.
 
1824
 
 
1825
        This method can only be called when the Undo/Redo mechanism has
 
1826
        been enabled.  Otherwise, an `UndoRedoError` is raised.
 
1827
        """
 
1828
 
 
1829
        self._checkOpen()
 
1830
        self._checkUndoEnabled()
 
1831
        return self._curmark
 
1832
 
 
1833
 
 
1834
    def _shadowName(self):
 
1835
        """
 
1836
        Compute and return a shadow name.
 
1837
 
 
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.
 
1841
        """
 
1842
 
 
1843
        parent = self.getNode(
 
1844
            _shadowParent % (self._curtransaction, self._curmark))
 
1845
        name = _shadowName % (self._curaction,)
 
1846
 
 
1847
        return (parent, name)
 
1848
 
 
1849
    # </Undo/Redo support>
 
1850
 
 
1851
 
949
1852
    def flush(self):
950
1853
        """Flush all the objects on all the HDF5 objects tree."""
951
1854
 
 
1855
        self._checkOpen()
 
1856
 
952
1857
        for group in self.walkGroups(self.root):
953
1858
            for leaf in self.listNodes(group, classname = 'Leaf'):
954
1859
                leaf.flush()
955
1860
 
956
1861
        # Flush the cache to disk
957
1862
        self._flushFile(0)  # 0 means local scope, 1 global (virtual) scope
958
 
                
959
 
    def close(self):        
960
 
        """Close all the objects in HDF5 file and close the file."""
 
1863
 
 
1864
 
 
1865
    def _closeDescendentsOf(self, group):
 
1866
        """Close all the *loaded* descendent nodes of the given `group`."""
 
1867
 
 
1868
        assert isinstance(group, Group)
 
1869
 
 
1870
        prefix = group._v_pathname + '/'
 
1871
        if prefix == '//':
 
1872
            prefix = '/'
 
1873
 
 
1874
        self._closeNodes(
 
1875
            [path for path in self._aliveNodes if path.startswith(prefix)])
 
1876
 
 
1877
        self._closeNodes(
 
1878
            [path for path in self._deadNodes if path.startswith(prefix)])
 
1879
 
 
1880
 
 
1881
    def _closeNodes(self, nodePaths, getNode=None):
 
1882
        """
 
1883
        Close all nodes in the list of `nodePaths`.
 
1884
 
 
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
 
1888
        ignored.
 
1889
        """
 
1890
 
 
1891
        if getNode is None:
 
1892
            getNode = self.getNode
 
1893
 
 
1894
        for nodePath in nodePaths:
 
1895
            try:
 
1896
                node = getNode(nodePath)
 
1897
                node._f_close()
 
1898
                del node
 
1899
            except KeyError:
 
1900
                pass
 
1901
 
 
1902
 
 
1903
    def close(self):
 
1904
        """Close all the nodes in HDF5 file and close the file."""
 
1905
        global _open_files
961
1906
 
962
1907
        # If the file is already closed, return immediately
963
1908
        if not self.isopen:
964
1909
            return
965
1910
 
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'):
970
 
                leaf.close()
971
 
            group._f_close()
972
 
            
 
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)
 
1915
 
 
1916
        # Close all loaded nodes.
 
1917
 
 
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())
 
1931
 
 
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])
 
1946
 
 
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())
 
1951
 
 
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()
 
1957
 
973
1958
        # Close the file
974
1959
        self._closeFile()
975
 
                    
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
981
1965
        self.isopen = 0
982
 
 
983
 
        return
 
1966
        # Delete the entry in the dictionary of opened files
 
1967
        del _open_files[self]
984
1968
 
985
1969
    def __str__(self):
986
1970
        """Returns a string representation of the object tree"""
987
 
        
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'
998
1981
            astring += str(group) + '\n'
999
1982
            for leaf in self.listNodes(group, 'Leaf'):
1000
1983
                astring += str(leaf) + '\n'
1001
 
                
1002
1984
        return astring
1003
1985
 
1004
1986
    def __repr__(self):
1005
1987
        """Returns a more complete representation of the object tree"""
1006
 
        
 
1988
 
 
1989
        if not self.isopen:
 
1990
            return "<closed File>"
 
1991
 
1007
1992
        # Print all the nodes (Group and Leaf objects) on object tree
1008
1993
        astring = 'File(filename=' + repr(self.filename) + \
1009
1994
                  ', title=' + repr(self.title) + \
1016
2001
            astring += str(group) + '\n'
1017
2002
            for leaf in self.listNodes(group, 'Leaf'):
1018
2003
                astring += repr(leaf) + '\n'
1019
 
                
1020
2004
        return astring
 
2005
 
 
2006
 
 
2007
    def _refNode(self, node, nodePath, useNode=True):
 
2008
        """
 
2009
        Register `node` as alive and insert references to it.
 
2010
 
 
2011
        If `useNode` is false, no methods or attributes of the node will
 
2012
        ever be accessed.  This is useful to avoid secondary effects
 
2013
        during close operations.
 
2014
        """
 
2015
 
 
2016
        if nodePath != '/':
 
2017
            # The root group does not participate in alive/dead stuff.
 
2018
            aliveNodes = self._aliveNodes
 
2019
            assert nodePath not in aliveNodes, \
 
2020
                   "file already has a node with path ``%s``" % nodePath
 
2021
 
 
2022
            # Add the node to the set of referenced ones.
 
2023
            aliveNodes[nodePath] = node
 
2024
 
 
2025
        # Add the node to the visible node mappings.
 
2026
        if useNode and isVisiblePath(nodePath):
 
2027
            # This is only done for visible nodes.
 
2028
            # Assigned values are entirely irrelevant.
 
2029
            self.objects[nodePath] = None
 
2030
            if isinstance(node, Leaf):
 
2031
                self.leaves[nodePath] = None
 
2032
            if isinstance(node, Group):
 
2033
                self.groups[nodePath] = None
 
2034
 
 
2035
 
 
2036
    def _unrefNode(self, nodePath):
 
2037
        """Unregister `node` as alive and remove references to it."""
 
2038
 
 
2039
        if nodePath != '/':
 
2040
            # The root group does not participate in alive/dead stuff.
 
2041
            aliveNodes = self._aliveNodes
 
2042
            assert nodePath in aliveNodes, \
 
2043
                   "file does not have a node with path ``%s``" % nodePath
 
2044
 
 
2045
            # Remove the node from the set of referenced ones.
 
2046
            del aliveNodes[nodePath]
 
2047
 
 
2048
        # Remove the node from the visible node mappings.
 
2049
        self.objects.pop(nodePath, None)
 
2050
        self.leaves.pop(nodePath, None)
 
2051
        self.groups.pop(nodePath, None)
 
2052
 
 
2053
 
 
2054
    def _killNode(self, node):
 
2055
        """
 
2056
        Kill the `node`.
 
2057
 
 
2058
        Moves the `node` from the set of alive, referenced nodes to the
 
2059
        set of dead, unreferenced ones.
 
2060
        """
 
2061
 
 
2062
        nodePath = node._v_pathname
 
2063
        assert nodePath in self._aliveNodes, \
 
2064
               "trying to kill non-alive node ``%s``" % nodePath
 
2065
 
 
2066
        node._g_preKillHook()
 
2067
 
 
2068
        # Remove all references to the node.
 
2069
        self._unrefNode(nodePath)
 
2070
        # Save the dead node in the limbo.
 
2071
        self._deadNodes[nodePath] = node
 
2072
 
 
2073
 
 
2074
    def _reviveNode(self, nodePath, useNode=True):
 
2075
        """
 
2076
        Revive the node under `nodePath` and return it.
 
2077
 
 
2078
        Moves the node under `nodePath` from the set of dead,
 
2079
        unreferenced nodes to the set of alive, referenced ones.
 
2080
 
 
2081
        If `useNode` is false, no methods or attributes of the node will
 
2082
        ever be accessed.  This is useful to avoid secondary effects
 
2083
        during close operations.
 
2084
        """
 
2085
 
 
2086
        assert nodePath in self._deadNodes, \
 
2087
               "trying to revive non-dead node ``%s``" % nodePath
 
2088
 
 
2089
        # Take the node out of the limbo.
 
2090
        node = self._deadNodes.pop(nodePath)
 
2091
        # Make references to the node.
 
2092
        self._refNode(node, nodePath, useNode)
 
2093
 
 
2094
        node._g_postReviveHook()
 
2095
 
 
2096
        return node
 
2097
 
 
2098
# If a users hits ^C during a run, it is wise to gracefully close the opened files.
 
2099
def close_open_files():
 
2100
    global _open_files
 
2101
    if len(_open_files):
 
2102
        print "Closing remaining opened files...",
 
2103
    for fileh in _open_files.keys():
 
2104
        print " %s..." % (fileh.filename,),
 
2105
        fileh.close()
 
2106
        print "done.",
 
2107
 
 
2108
import atexit
 
2109
atexit.register(close_open_files)
 
2110
 
 
2111
 
 
2112
## Local Variables:
 
2113
## mode: python
 
2114
## py-indent-offset: 4
 
2115
## tab-width: 4
 
2116
## fill-column: 72
 
2117
## End: