1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4#       Copyright (C) 2005-2007 Carabos Coop. V. All rights reserved
5#       Copyright (C) 2008-2019 Vicent Mas. All rights reserved
6#
7#       This program is free software: you can redistribute it and/or modify
8#       it under the terms of the GNU General Public License as published by
9#       the Free Software Foundation, either version 3 of the License, or
10#       (at your option) any later version.
11#
12#       This program is distributed in the hope that it will be useful,
13#       but WITHOUT ANY WARRANTY; without even the implied warranty of
14#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#       GNU General Public License for more details.
16#
17#       You should have received a copy of the GNU General Public License
18#       along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20#       Author:  Vicent Mas - vmas@vitables.org
21
22"""
23This module collects information about a given node of a `PyTables` database.
24"""
25
26__docformat__ = 'restructuredtext'
27
28import os.path
29
30import vitables.utils
31
32class NodeInfo(object):
33    """Collects information about a given node.
34
35    The following data and metadata can be collected:
36
37    * format of the database (generic `HDF5` or `PyTables`)
38    * filename of the database
39    * filepath of the database
40    * opening mode of the database
41    * size of the database
42    * node type
43    * node name
44    * node path
45    * for `tables.Group` nodes:
46
47        - attributes set instance and related info
48        - dictionary of nodes hanging from a group
49        - dictionary of groups hanging from a group
50        - dictionary of leaves hanging from a group
51
52    * for `tables.Table` nodes:
53
54        - columns names, datatypes and shapes
55        - number of rows and columns
56        - filters
57        - shape
58        - flavor
59
60    * for `tables.XArray` nodes:
61
62        - number of rows
63        - datatype
64        - filters
65        - shape
66        - flavor
67
68    :Parameter node_item: an instance of :meth:`RootGroupNode`,
69      :meth:`GroupNode` or :meth:`LeafNode`.
70    """
71
72    def __init__(self, node_item):
73        """Collects information about a given node.
74
75        Some PyTables2.X string attributes are regular Python strings instead
76        of unicode strings and have to be explicitely converted to unicode.
77        """
78
79        self.node = node_item.node
80
81        # The hosting File instance, filepath, filename and opening mode
82        self.filepath = str(node_item.filepath)
83        self.filename = os.path.basename(self.filepath)
84        self.h5file = self.node._v_file
85        mode = str(self.h5file.mode)
86        if mode == 'a':
87            self.mode = 'append'
88        elif mode == 'r':
89            self.mode = 'read-only'
90        else:
91            self.mode = 'read-write'
92
93        # The node type is a string with one of the following values:
94        # root group, group, table, vlarray, earray, carray, array
95        # or unimplemented
96        self.node_type = node_item.node_kind
97        self.file_type = self.format + ', ' + self.size
98        self.nodename = str(node_item.name)
99        self.nodepath = str(node_item.nodepath)
100
101        # The attributes set instance
102        self.asi = self.node._v_attrs
103        sysattrs_names = self.asi._v_attrnamessys
104        self.system_attrs = \
105            dict((n, getattr(self.asi, n, None)) for n in sysattrs_names)
106        userattrs_names = self.asi._v_attrnamesuser
107        self.user_attrs = \
108            dict((n, getattr(self.asi, n, None)) for n in userattrs_names)
109
110    # Properties for File instances
111
112    def _format(self):
113        """The format of the hosting `tables.File` instance"""
114
115        if self.h5file._isPTFile:
116            return 'PyTables file'
117        else:
118            return 'Generic HDF5 file'
119
120    format = property(fget=_format)
121
122
123    def _size(self):
124        """The size of the hosting `tables.File` instance"""
125
126        n_bytes = os.path.getsize(self.filepath) *1.0
127        kbytes = n_bytes/1024
128        mbytes = kbytes/1024
129        gbytes = mbytes/1024
130        tbytes = gbytes/1024
131        if kbytes < 1:
132            size = '{0:.0f} bytes'.format(n_bytes)
133        elif mbytes < 1:
134            size = '{0:.1f} KB'.format(kbytes)
135        elif gbytes < 1:
136            size = '{0:.1f} MB'.format(mbytes)
137        elif tbytes < 1:
138            size = '{0:.1f} GB'.format(gbytes)
139        else:
140            size = '{0:.1f} TB'.format(tbytes)
141        return size
142
143    size = property(fget=_size)
144
145    # Properties for Group instances
146
147    def _hangingNodes(self):
148        """The dictionary of nodes hanging from this node."""
149
150        try:
151            return self.node._v_children
152        except AttributeError:
153            return {}
154
155    hanging_nodes = property(fget=_hangingNodes)
156
157
158    def _hangingGroups(self):
159        """The dictionary of groups hanging from this node."""
160
161        try:
162            return self.node._v_groups
163        except AttributeError:
164            return {}
165
166    hanging_groups = property(fget=_hangingGroups)
167
168
169    def _hangingLeaves(self):
170        """The dictionary of leaves hanging from this node."""
171
172        try:
173            return self.node._v_leaves
174        except AttributeError:
175            return {}
176
177    hanging_leaves = property(fget=_hangingLeaves)
178
179
180    def _hangingLinks(self):
181        """The dictionary of links hanging from this node."""
182
183        try:
184            return self.node._v_links
185        except AttributeError:
186            return {}
187
188    hanging_links = property(fget=_hangingLinks)
189
190
191    # Properties for Leaf instances
192
193    def _type(self):
194        """The `PyTables` data type of the atom for `tables.Leaf` nodes."""
195
196        if self.node_type.count('array'):
197            try:
198                return str(self.node.atom.type)
199            except AttributeError:
200                return None
201        elif self.node_type == 'table':
202            return 'record'
203
204    type = property(fget=_type)
205
206
207    def _nrows(self):
208        """The current number of rows in the `tables.Leaf` node."""
209
210        try:
211            return self.node.shape[0]
212        except TypeError:  #  shape is None
213            return 0
214        except IndexError:  # numpy scalar arrays have shape = ()
215            return 1
216
217    nrows = property(fget=_nrows)
218
219
220    def _shape(self):
221        """The shape of data in the `tables.Leaf` node."""
222
223        try:
224            return self.node.shape
225        except AttributeError:
226            return None
227
228    shape = property(fget=_shape)
229
230
231    def _flavor(self):
232        """The type of data object read from the `tables.Leaf` node."""
233
234        try:
235            return str(self.node.flavor)
236        except AttributeError:
237            return None
238
239    flavor = property(fget=_flavor)
240
241
242    def _filters(self):
243        """Filters property for this `tables.Leaf` node."""
244
245        try:
246            return self.node.filters
247        except AttributeError:
248            return None
249
250    filters = property(fget=_filters)
251
252    # Properties for Table instances
253
254    def _colNames(self):
255        """The list of names of top-level columns in a `tables.Table` node."""
256
257        try:
258            return self.node.colnames
259        except AttributeError:
260            return []
261
262    columns_names = property(fget=_colNames)
263
264
265    def _colPathNames(self):
266        """The list of paths of top-level columns in a `tables.Table` node."""
267
268        try:
269            return self.node.colpathnames
270        except AttributeError:
271            return []
272
273    columns_pathnames = property(fget=_colPathNames)
274
275
276    def _colTypes(self):
277        """
278        Mapping with `tables.Table` field names and their `PyTables` datatypes.
279        """
280
281        try:
282            return self.node.coltypes
283        except AttributeError:
284            return {}
285
286    columns_types = property(fget=_colTypes)
287
288
289    def _colShapes(self):
290        """
291        Mapping with the `tables.Table` field names and their shapes.
292        """
293
294        try:
295            coldescrs = self.node.coldescrs
296            return dict((k, v.shape) for (k, v) in coldescrs.items())
297        except AttributeError:
298            return {}
299
300    columns_shapes = property(fget=_colShapes)
301
302
303    def _ncolumns(self):
304        """The current number of columns in the `tables.Table` node."""
305
306        try:
307            return len(self.node.columns_names)
308        except AttributeError:
309            return None
310
311    ncolumns = property(fget=_ncolumns)
312
313
314    def _target(self):
315        """The target of a `tables.Link` node."""
316
317        try:
318            return str(self.node.target)
319        except AttributeError:
320            return None
321
322    target = property(fget=_target)
323
324
325    def _link_type(self):
326        """The kind of link (i.e. soft or external) of a `tables.Link` node."""
327
328        if self.target:
329            try:
330                link_type = 'external'
331                self.node.extfile
332            except AttributeError:
333                link_type = 'soft'
334            return link_type
335        else:
336            return None
337
338    link_type = property(fget=_link_type)
339
340
341