dataformat.py 17.4 KB
Newer Older
André Anjos's avatar
André Anjos committed
1
2
3
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

Samuel GAIST's avatar
Samuel GAIST committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
###################################################################################
#                                                                                 #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
# Contact: beat.support@idiap.ch                                                  #
#                                                                                 #
# Redistribution and use in source and binary forms, with or without              #
# modification, are permitted provided that the following conditions are met:     #
#                                                                                 #
# 1. Redistributions of source code must retain the above copyright notice, this  #
# list of conditions and the following disclaimer.                                #
#                                                                                 #
# 2. Redistributions in binary form must reproduce the above copyright notice,    #
# this list of conditions and the following disclaimer in the documentation       #
# and/or other materials provided with the distribution.                          #
#                                                                                 #
# 3. Neither the name of the copyright holder nor the names of its contributors   #
# may be used to endorse or promote products derived from this software without   #
# specific prior written permission.                                              #
#                                                                                 #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
#                                                                                 #
###################################################################################
André Anjos's avatar
André Anjos committed
35
36


37
38
39
40
41
42
43
"""
==========
dataformat
==========

Validation and parsing for dataformats
"""
André Anjos's avatar
André Anjos committed
44
45
46
47
48
49
50
51

import re
import copy

import six
import numpy
import simplejson

52
from . import utils
André Anjos's avatar
André Anjos committed
53
54
55
from .baseformat import baseformat


56
# ----------------------------------------------------------
57

58
59

class Storage(utils.Storage):
Philip ABBET's avatar
Philip ABBET committed
60
    """Resolves paths for dataformats
61

Philip ABBET's avatar
Philip ABBET committed
62
    Parameters:
63

64
      prefix (str): Establishes the prefix of your installation.
65

Philip ABBET's avatar
Philip ABBET committed
66
67
      name (str): The name of the dataformat object in the format
        ``<user>/<name>/<version>``.
68

Philip ABBET's avatar
Philip ABBET committed
69
    """
70

71
72
73
    asset_type = "dataformat"
    asset_folder = "dataformats"

Philip ABBET's avatar
Philip ABBET committed
74
    def __init__(self, prefix, name):
75

Samuel GAIST's avatar
Samuel GAIST committed
76
        if name.count("/") != 2:
Philip ABBET's avatar
Philip ABBET committed
77
            raise RuntimeError("invalid dataformat name: `%s'" % name)
78

Samuel GAIST's avatar
Samuel GAIST committed
79
        self.username, self.name, self.version = name.split("/")
Philip ABBET's avatar
Philip ABBET committed
80
        self.fullname = name
81
        self.prefix = prefix
82

83
84
85
        path = utils.hashed_or_simple(
            self.prefix, self.asset_folder, name, suffix=".json"
        )
86
        path = path[:-5]
Philip ABBET's avatar
Philip ABBET committed
87
        super(Storage, self).__init__(path)
88

Philip ABBET's avatar
Philip ABBET committed
89
90
    def hash(self):
        """The 64-character hash of the database declaration JSON"""
Samuel GAIST's avatar
Samuel GAIST committed
91
        return super(Storage, self).hash("#description")
92
93


94
# ----------------------------------------------------------
95

96

André Anjos's avatar
André Anjos committed
97
class DataFormat(object):
Philip ABBET's avatar
Philip ABBET committed
98
    """Data formats define the chunks of data that circulate between blocks.
André Anjos's avatar
André Anjos committed
99

Philip ABBET's avatar
Philip ABBET committed
100
    Parameters:
André Anjos's avatar
André Anjos committed
101

102
      prefix (str): Establishes the prefix of
103
        your installation.
André Anjos's avatar
André Anjos committed
104

Philip ABBET's avatar
Philip ABBET committed
105
106
      data (str, dict): The fully qualified algorithm name (e.g. ``user/algo/1``)
        or a dictionary representing the data format (for analyzer results).
André Anjos's avatar
André Anjos committed
107

108
109
110
111
112
113
114
115
116
117
118
119
120
      parent (:py:class:`tuple`, Optional): The parent DataFormat for this
        format. If set to ``None``, this means this dataformat is the first one
        on the hierarchy tree. If set to a tuple, the contents are
        ``(format-instance, field-name)``, which indicates the originating
        object that is this object's parent and the name of the field on that
        object that points to this one.

      dataformat_cache (:py:class:`dict`, Optional): A dictionary mapping
        dataformat names to loaded dataformats. This parameter is optional and,
        if passed, may greatly speed-up data format loading times as
        dataformats that are already loaded may be re-used. If you use this
        parameter, you must guarantee that the cache is refreshed as
        appropriate in case the underlying dataformats change.
André Anjos's avatar
André Anjos committed
121

Philip ABBET's avatar
Philip ABBET committed
122
    Attributes:
André Anjos's avatar
André Anjos committed
123

Philip ABBET's avatar
Philip ABBET committed
124
      name (str): The full, valid name of this dataformat
André Anjos's avatar
André Anjos committed
125

Philip ABBET's avatar
Philip ABBET committed
126
127
      description (str): The short description string, loaded from the JSON
        file if one was set.
128

Philip ABBET's avatar
Philip ABBET committed
129
      documentation (str): The full-length docstring for this object.
130

Philip ABBET's avatar
Philip ABBET committed
131
132
      storage (object): A simple object that provides information about file
        paths for this dataformat
133

134
      errors (list): A list strings containing errors found while loading this
Philip ABBET's avatar
Philip ABBET committed
135
        dataformat.
136

Philip ABBET's avatar
Philip ABBET committed
137
138
      data (dict): The original data for this dataformat, as loaded by our JSON
        decoder.
André Anjos's avatar
André Anjos committed
139

Philip ABBET's avatar
Philip ABBET committed
140
141
      resolved (dict): A dictionary similar to :py:attr:`data`, but with
        references fully resolved.
André Anjos's avatar
André Anjos committed
142

Philip ABBET's avatar
Philip ABBET committed
143
      referenced (dict): A dictionary pointing to all loaded dataformats.
André Anjos's avatar
André Anjos committed
144

145
      parent (dataformat.DataFormat): The pointer to the
Philip ABBET's avatar
Philip ABBET committed
146
147
        dataformat to which the current format is part of. It is useful for
        internal error reporting.
André Anjos's avatar
André Anjos committed
148

Philip ABBET's avatar
Philip ABBET committed
149
    """
André Anjos's avatar
André Anjos committed
150

Philip ABBET's avatar
Philip ABBET committed
151
    def __init__(self, prefix, data, parent=None, dataformat_cache=None):
André Anjos's avatar
André Anjos committed
152

Philip ABBET's avatar
Philip ABBET committed
153
154
155
156
157
158
159
160
161
        self._name = None
        self.storage = None
        self.resolved = None
        self.prefix = prefix
        self.errors = []
        self.data = None
        self.resolved = None
        self.referenced = {}
        self.parent = parent
André Anjos's avatar
André Anjos committed
162

Philip ABBET's avatar
Philip ABBET committed
163
164
        # if the user has not provided a cache, still use one for performance
        dataformat_cache = dataformat_cache if dataformat_cache is not None else {}
André Anjos's avatar
André Anjos committed
165

Philip ABBET's avatar
Philip ABBET committed
166
167
168
        try:
            self._load(data, dataformat_cache)
        finally:
169
            if self._name is not None:  # registers it into the cache, even if failed
Philip ABBET's avatar
Philip ABBET committed
170
                dataformat_cache[self._name] = self
171

Philip ABBET's avatar
Philip ABBET committed
172
173
    def _load(self, data, dataformat_cache):
        """Loads the dataformat"""
174

Philip ABBET's avatar
Philip ABBET committed
175
        if isinstance(data, dict):
Samuel GAIST's avatar
Samuel GAIST committed
176
            self._name = "analysis:result"
Philip ABBET's avatar
Philip ABBET committed
177
178
179
180
181
182
            self.data = data
        else:
            self._name = data
            self.storage = Storage(self.prefix, data)
            json_path = self.storage.json.path
            if not self.storage.exists():
Samuel GAIST's avatar
Samuel GAIST committed
183
184
185
                self.errors.append(
                    "Dataformat declaration file not found: %s" % json_path
                )
Philip ABBET's avatar
Philip ABBET committed
186
                return
André Anjos's avatar
André Anjos committed
187

Samuel GAIST's avatar
Samuel GAIST committed
188
189
            with open(json_path, "rb") as f:
                self.data = simplejson.loads(f.read().decode("utf-8"))
190

191
        dataformat_cache[self._name] = self  # registers itself into the cache
André Anjos's avatar
André Anjos committed
192

Philip ABBET's avatar
Philip ABBET committed
193
        self.resolved = copy.deepcopy(self.data)
André Anjos's avatar
André Anjos committed
194

Philip ABBET's avatar
Philip ABBET committed
195
196
        # remove reserved fields
        def is_reserved(x):
Samuel GAIST's avatar
Samuel GAIST committed
197
198
199
200
201
            """Returns if the field name is a reserved name"""
            return (x.startswith("__") and x.endswith("__")) or x in (
                "#description",
                "#schema_version",
            )
André Anjos's avatar
André Anjos committed
202

Philip ABBET's avatar
Philip ABBET committed
203
        for key in list(self.resolved):
Samuel GAIST's avatar
Samuel GAIST committed
204
205
            if is_reserved(key):
                del self.resolved[key]
André Anjos's avatar
André Anjos committed
206

Philip ABBET's avatar
Philip ABBET committed
207
208
        def maybe_load_format(name, obj, dataformat_cache):
            """Tries to load a given dataformat from its relative path"""
André Anjos's avatar
André Anjos committed
209

Samuel GAIST's avatar
Samuel GAIST committed
210
            if isinstance(obj, six.string_types) and obj.find("/") != -1:  # load it
André Anjos's avatar
André Anjos committed
211

212
                if obj in dataformat_cache:  # reuse
André Anjos's avatar
André Anjos committed
213

214
                    if dataformat_cache[obj] is None:  # recursion detected
Philip ABBET's avatar
Philip ABBET committed
215
                        return self
André Anjos's avatar
André Anjos committed
216

Philip ABBET's avatar
Philip ABBET committed
217
                    self.referenced[obj] = dataformat_cache[obj]
André Anjos's avatar
André Anjos committed
218

219
                else:  # load it
Samuel GAIST's avatar
Samuel GAIST committed
220
221
222
                    self.referenced[obj] = DataFormat(
                        self.prefix, obj, (self, name), dataformat_cache
                    )
André Anjos's avatar
André Anjos committed
223

Philip ABBET's avatar
Philip ABBET committed
224
                return self.referenced[obj]
André Anjos's avatar
André Anjos committed
225

226
            elif isinstance(obj, dict):  # can cache it, must load from scratch
Philip ABBET's avatar
Philip ABBET committed
227
                return DataFormat(self.prefix, obj, (self, name), dataformat_cache)
André Anjos's avatar
André Anjos committed
228

Philip ABBET's avatar
Philip ABBET committed
229
230
231
232
            elif isinstance(obj, list):
                retval = copy.deepcopy(obj)
                retval[-1] = maybe_load_format(field, obj[-1], dataformat_cache)
                return retval
André Anjos's avatar
André Anjos committed
233

Philip ABBET's avatar
Philip ABBET committed
234
            return obj
André Anjos's avatar
André Anjos committed
235

Philip ABBET's avatar
Philip ABBET committed
236
237
        # now checks that every referred dataformat is loaded, resolves it
        for field, value in self.data.items():
Samuel GAIST's avatar
Samuel GAIST committed
238
            if field in ("#description", "#schema_version"):
239
                continue  # skip the description and schema version meta attributes
Philip ABBET's avatar
Philip ABBET committed
240
            self.resolved[field] = maybe_load_format(field, value, dataformat_cache)
André Anjos's avatar
André Anjos committed
241

Philip ABBET's avatar
Philip ABBET committed
242
243
244
        # at this point, there should be no more external references in
        # ``self.resolved``. We treat the "#extends" property, which requires a
        # special handling, given its nature.
Samuel GAIST's avatar
Samuel GAIST committed
245
        if "#extends" in self.resolved:
André Anjos's avatar
André Anjos committed
246

Samuel GAIST's avatar
Samuel GAIST committed
247
            ext = self.data["#extends"]
Philip ABBET's avatar
Philip ABBET committed
248
            self.referenced[ext] = maybe_load_format(self._name, ext, dataformat_cache)
Samuel GAIST's avatar
Samuel GAIST committed
249
            basetype = self.resolved["#extends"]
Philip ABBET's avatar
Philip ABBET committed
250
251
252
            tmp = self.resolved
            self.resolved = basetype.resolved
            self.resolved.update(tmp)
Samuel GAIST's avatar
Samuel GAIST committed
253
            del self.resolved["#extends"]  # avoids infinite recursion
André Anjos's avatar
André Anjos committed
254

Philip ABBET's avatar
Philip ABBET committed
255
256
257
258
259
260
    @property
    def name(self):
        """Returns the name of this object, either from the filename or composed
        from the hierarchy it belongs.
        """
        if self.parent and self._name is None:
Samuel GAIST's avatar
Samuel GAIST committed
261
            return self.parent[0].name + "." + self.parent[1] + "_type"
Philip ABBET's avatar
Philip ABBET committed
262
        else:
Samuel GAIST's avatar
Samuel GAIST committed
263
            return self._name or "__unnamed_dataformat__"
264

Philip ABBET's avatar
Philip ABBET committed
265
266
267
268
    @name.setter
    def name(self, value):
        self._name = value
        self.storage = Storage(self.prefix, value)
269

Philip ABBET's avatar
Philip ABBET committed
270
271
272
    @property
    def schema_version(self):
        """Returns the schema version"""
Samuel GAIST's avatar
Samuel GAIST committed
273
        return self.data.get("#schema_version", 1)
André Anjos's avatar
André Anjos committed
274

Philip ABBET's avatar
Philip ABBET committed
275
276
277
278
    @property
    def extends(self):
        """If this dataformat extends another one, this is it, otherwise ``None``
        """
Samuel GAIST's avatar
Samuel GAIST committed
279
        return self.data.get("#extends")
André Anjos's avatar
André Anjos committed
280

Philip ABBET's avatar
Philip ABBET committed
281
282
283
    @property
    def type(self):
        """Returns a new type that can create instances of this dataformat.
André Anjos's avatar
André Anjos committed
284

Philip ABBET's avatar
Philip ABBET committed
285
286
287
        The new returned type provides a basis to construct new objects which
        represent the dataformat. It provides a simple JSON serializer and a
        for-screen representation.
André Anjos's avatar
André Anjos committed
288

Philip ABBET's avatar
Philip ABBET committed
289
        Example:
André Anjos's avatar
André Anjos committed
290

291
292
          To create an object respecting the data format from a JSON
          descriptor, use the following technique:
André Anjos's avatar
André Anjos committed
293

Philip ABBET's avatar
Philip ABBET committed
294
          .. code-block:: python
André Anjos's avatar
André Anjos committed
295

296
297
298
            ftype = dataformat(...).type
            json = simplejson.loads(...)
            newobj = ftype(**json) # instantiates the new object, checks format
André Anjos's avatar
André Anjos committed
299

Philip ABBET's avatar
Philip ABBET committed
300
          To dump the object into JSON, use the following technique:
André Anjos's avatar
André Anjos committed
301

Philip ABBET's avatar
Philip ABBET committed
302
          .. code-block:: python
André Anjos's avatar
André Anjos committed
303

Philip ABBET's avatar
Philip ABBET committed
304
             simplejson.dumps(newobj.as_dict(), indent=4)
André Anjos's avatar
André Anjos committed
305

Philip ABBET's avatar
Philip ABBET committed
306
307
308
          A string representation of the object uses the technique above to
          pretty-print the object contents to the screen.
        """
André Anjos's avatar
André Anjos committed
309

Philip ABBET's avatar
Philip ABBET committed
310
311
        if self.resolved is None:
            raise RuntimeError("Cannot prototype while not properly initialized")
André Anjos's avatar
André Anjos committed
312

Samuel GAIST's avatar
Samuel GAIST committed
313
314
315
        classname = re.sub(r"[-/]", "_", self.name)
        if not isinstance(classname, str):
            classname = str(classname)
André Anjos's avatar
André Anjos committed
316

Samuel GAIST's avatar
Samuel GAIST committed
317
318
        def init(self, **kwargs):
            baseformat.__init__(self, **kwargs)
André Anjos's avatar
André Anjos committed
319

Samuel GAIST's avatar
Samuel GAIST committed
320
        attributes = dict(__init__=init, _name=self.name, _format=self.resolved)
André Anjos's avatar
André Anjos committed
321

Philip ABBET's avatar
Philip ABBET committed
322
323
        # create the converters for the class we're about to return
        for k, v in self.resolved.items():
André Anjos's avatar
André Anjos committed
324

325
            if isinstance(v, list):  # it is an array
Philip ABBET's avatar
Philip ABBET committed
326
327
328
329
                attributes[k] = copy.deepcopy(v)
                if isinstance(v[-1], DataFormat):
                    attributes[k][-1] = v[-1].type
                else:
Samuel GAIST's avatar
Samuel GAIST committed
330
                    if v[-1] in ("string", "str"):
Philip ABBET's avatar
Philip ABBET committed
331
332
333
                        attributes[k][-1] = str
                    else:
                        attributes[k][-1] = numpy.dtype(v[-1])
André Anjos's avatar
André Anjos committed
334

335
            elif isinstance(v, DataFormat):  # it is another dataformat
Philip ABBET's avatar
Philip ABBET committed
336
                attributes[k] = v.type
André Anjos's avatar
André Anjos committed
337

338
            else:  # it is a simple type
Samuel GAIST's avatar
Samuel GAIST committed
339
                if v in ("string", "str"):
Philip ABBET's avatar
Philip ABBET committed
340
341
342
                    attributes[k] = str
                else:
                    attributes[k] = numpy.dtype(v)
André Anjos's avatar
André Anjos committed
343

Samuel GAIST's avatar
Samuel GAIST committed
344
        return type(classname, (baseformat,), attributes)
André Anjos's avatar
André Anjos committed
345

Philip ABBET's avatar
Philip ABBET committed
346
347
    @property
    def valid(self):
348
        """A boolean that indicates if this dataformat is valid or not"""
Philip ABBET's avatar
Philip ABBET committed
349
        return not bool(self.errors)
350

Philip ABBET's avatar
Philip ABBET committed
351
352
353
    @property
    def description(self):
        """The short description for this object"""
Samuel GAIST's avatar
Samuel GAIST committed
354
        return self.data.get("#description", None)
355

Philip ABBET's avatar
Philip ABBET committed
356
357
358
    @description.setter
    def description(self, value):
        """Sets the short description for this object"""
Samuel GAIST's avatar
Samuel GAIST committed
359
        self.data["#description"] = value
360

Philip ABBET's avatar
Philip ABBET committed
361
362
363
    @property
    def documentation(self):
        """The full-length description for this object"""
364

Philip ABBET's avatar
Philip ABBET committed
365
366
        if not self._name:
            raise RuntimeError("dataformat has no name")
367

Philip ABBET's avatar
Philip ABBET committed
368
369
370
        if self.storage.doc.exists():
            return self.storage.doc.load()
        return None
371

Philip ABBET's avatar
Philip ABBET committed
372
373
374
    @documentation.setter
    def documentation(self, value):
        """Sets the full-length description for this object"""
375

Philip ABBET's avatar
Philip ABBET committed
376
377
        if not self._name:
            raise RuntimeError("dataformat has no name")
378

Samuel GAIST's avatar
Samuel GAIST committed
379
        if hasattr(value, "read"):
Philip ABBET's avatar
Philip ABBET committed
380
381
382
            self.storage.doc.save(value.read())
        else:
            self.storage.doc.save(value)
383

Philip ABBET's avatar
Philip ABBET committed
384
385
    def hash(self):
        """Returns the hexadecimal hash for its declaration"""
386

Philip ABBET's avatar
Philip ABBET committed
387
388
        if not self._name:
            raise RuntimeError("dataformat has no name")
389

Philip ABBET's avatar
Philip ABBET committed
390
        return self.storage.hash()
391

Philip ABBET's avatar
Philip ABBET committed
392
393
    def validate(self, data):
        """Validates a piece of data provided by the user
André Anjos's avatar
André Anjos committed
394

395
396
397
398
399
        In order to validate, the data object must be complete and
        safe-castable to this dataformat. For any other validation operation
        that would require special settings, use instead the :py:meth:`type`
        method to generate a valid type and use either ``from_dict``,
        ``unpack`` or ``unpack_from`` depending on your use-case.
André Anjos's avatar
André Anjos committed
400

Philip ABBET's avatar
Philip ABBET committed
401
        Parameters:
André Anjos's avatar
André Anjos committed
402

403
404
405
406
407
          data (dict, str, :std:term:`file object`): This parameter represents
            the data to be validated.  It may be a dictionary with the JSON
            representation of a data blob or, else, a binary blob (represented
            by either a string or a file descriptor object) from which the data
            will be read. If problems occur, an exception is raised.
André Anjos's avatar
André Anjos committed
408

Philip ABBET's avatar
Philip ABBET committed
409
        Returns:
André Anjos's avatar
André Anjos committed
410

Philip ABBET's avatar
Philip ABBET committed
411
412
          ``None``: Raises if an error occurs.
        """
André Anjos's avatar
André Anjos committed
413

Philip ABBET's avatar
Philip ABBET committed
414
415
        obj = self.type()
        if isinstance(data, dict):
Samuel GAIST's avatar
Samuel GAIST committed
416
            obj.from_dict(data, casting="safe", add_defaults=False)
Philip ABBET's avatar
Philip ABBET committed
417
418
419
420
        elif isinstance(data, six.string_types):
            obj.unpack(data)
        else:
            obj.unpack_from(data)
André Anjos's avatar
André Anjos committed
421

Philip ABBET's avatar
Philip ABBET committed
422
423
    def isparent(self, other):
        """Tells if the other object extends self (directly or indirectly).
André Anjos's avatar
André Anjos committed
424

Philip ABBET's avatar
Philip ABBET committed
425
        Parameters:
André Anjos's avatar
André Anjos committed
426

Philip ABBET's avatar
Philip ABBET committed
427
          other (DataFormat): another object to check
André Anjos's avatar
André Anjos committed
428
429


Philip ABBET's avatar
Philip ABBET committed
430
        Returns:
André Anjos's avatar
André Anjos committed
431

Philip ABBET's avatar
Philip ABBET committed
432
433
          bool: ``True``, if ``other`` is a parent of ``self``. ``False``
            otherwise.
André Anjos's avatar
André Anjos committed
434

Philip ABBET's avatar
Philip ABBET committed
435
        """
André Anjos's avatar
André Anjos committed
436

Philip ABBET's avatar
Philip ABBET committed
437
438
439
440
441
        if other.extends:
            if self.name == other.extends:
                return True
            else:
                return self.isparent(other.referenced[other.extends])
André Anjos's avatar
André Anjos committed
442

Philip ABBET's avatar
Philip ABBET committed
443
        return False
444
445
446
447
448
449

    def json_dumps(self, indent=4):
        """Dumps the JSON declaration of this object in a string

        Parameters:

450
451
          indent (int): The number of indentation spaces at every indentation
            level
452
453
454
455
456
457
458
459


        Returns:

          str: The JSON representation for this object

        """

Samuel GAIST's avatar
Samuel GAIST committed
460
        return simplejson.dumps(self.data, indent=indent, cls=utils.NumpyJSONEncoder)
461
462
463
464
465
466
467
468
469

    def __str__(self):
        return self.json_dumps()

    def write(self, storage=None):
        """Writes contents to prefix location

        Parameters:

470
471
472
          storage (:py:class:`.Storage`, Optional): If you pass a new storage,
            then this object will be written to that storage point rather than
            its default.
473
474
475
476
477
478

        """

        if storage is None:
            if not self._name:
                raise RuntimeError("dataformat has no name")
479
            storage = self.storage  # overwrite
480
481
482
483
484
485
486
487
488
489
490

        storage.save(str(self), self.description)

    def export(self, prefix):
        """Recursively exports itself into another prefix

        Other required dataformats are also copied.


        Parameters:

491
          prefix (str): Establishes the prefix of your installation.
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510


        Returns:

          None


        Raises:

          RuntimeError: If prefix and self.prefix point to the same directory.

        """

        if not self._name:
            raise RuntimeError("dataformat has no name")

        if not self.valid:
            raise RuntimeError("dataformat is not valid")

511
        if prefix == self.prefix:
Samuel GAIST's avatar
Samuel GAIST committed
512
513
514
            raise RuntimeError(
                "Cannot export dataformat to the same prefix (" "%s)" % prefix
            )
515
516
517
518
519

        for k in self.referenced.values():
            k.export(prefix)

        self.write(Storage(prefix, self.name))