Commit 235798e9 authored by Philip ABBET's avatar Philip ABBET

Refactoring of the 'DataFormat' class (which was duplicated in beat.core)

parent d58add06
......@@ -36,11 +36,43 @@ import six
import numpy
import simplejson
from . import utils
from .baseformat import baseformat
class Storage(utils.Storage):
"""Resolves paths for dataformats
Parameters:
prefix (str): Establishes the prefix of your installation.
name (str): The name of the dataformat object in the format
``<user>/<name>/<version>``.
"""
def __init__(self, prefix, name):
if name.count('/') != 2:
raise RuntimeError("invalid dataformat name: `%s'" % name)
self.username, self.name, self.version = name.split('/')
self.fullname = name
path = utils.hashed_or_simple(prefix, 'dataformats', name)
super(Storage, self).__init__(path)
def hash(self):
"""The 64-character hash of the database declaration JSON"""
return super(Storage, self).hash('#description')
class DataFormat(object):
"""Data formats define the chunks of data that circulate at data formats.
"""Data formats define the chunks of data that circulate between blocks.
Parameters:
......@@ -67,6 +99,17 @@ class DataFormat(object):
name (str): The full, valid name of this dataformat
description (str): The short description string, loaded from the JSON
file if one was set.
documentation (str): The full-length docstring for this object.
storage (object): A simple object that provides information about file
paths for this dataformat
errors (list of str): A list containing errors found while loading this
dataformat.
data (dict): The original data for this dataformat, as loaded by our JSON
decoder.
......@@ -83,23 +126,44 @@ class DataFormat(object):
def __init__(self, prefix, data, parent=None, dataformat_cache=None):
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
# if the user has not provided a cache, still use one for performance
dataformat_cache = dataformat_cache if dataformat_cache is not None else {}
try:
self._load(data, dataformat_cache)
finally:
if self._name is not None: #registers it into the cache, even if failed
dataformat_cache[self._name] = self
def _load(self, data, dataformat_cache):
"""Loads the dataformat"""
if isinstance(data, dict):
self.name = 'analysis:result'
self._name = 'analysis:result'
self.data = data
else:
self.name = data
json_path = os.path.join(prefix, 'dataformats', data + '.json')
with open(json_path, 'rb') as f: self.data = simplejson.load(f)
self._name = data
self.storage = Storage(self.prefix, data)
json_path = self.storage.json.path
if not self.storage.exists():
self.errors.append('Dataformat declaration file not found: %s' % json_path)
return
dataformat_cache[self.name] = self #registers itself into the cache
with open(json_path, 'rb') as f:
self.data = simplejson.load(f)
dataformat_cache[self._name] = self #registers itself into the cache
self.resolved = copy.deepcopy(self.data)
......@@ -152,7 +216,7 @@ class DataFormat(object):
if '#extends' in self.resolved:
ext = self.data['#extends']
self.referenced[ext] = maybe_load_format(self.name, ext, dataformat_cache)
self.referenced[ext] = maybe_load_format(self._name, ext, dataformat_cache)
basetype = self.resolved['#extends']
tmp = self.resolved
self.resolved = basetype.resolved
......@@ -160,6 +224,23 @@ class DataFormat(object):
del self.resolved['#extends'] #avoids infinite recursion
@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:
return self.parent[0].name + '.' + self.parent[1] + '_type'
else:
return self._name or '__unnamed_dataformat__'
@name.setter
def name(self, value):
self._name = value
self.storage = Storage(self.prefix, value)
@property
def schema_version(self):
"""Returns the schema version"""
......@@ -245,6 +326,57 @@ class DataFormat(object):
)
@property
def valid(self):
return not bool(self.errors)
@property
def description(self):
"""The short description for this object"""
return self.data.get('#description', None)
@description.setter
def description(self, value):
"""Sets the short description for this object"""
self.data['#description'] = value
@property
def documentation(self):
"""The full-length description for this object"""
if not self._name:
raise RuntimeError("dataformat has no name")
if self.storage.doc.exists():
return self.storage.doc.load()
return None
@documentation.setter
def documentation(self, value):
"""Sets the full-length description for this object"""
if not self._name:
raise RuntimeError("dataformat has no name")
if hasattr(value, 'read'):
self.storage.doc.save(value.read())
else:
self.storage.doc.save(value)
def hash(self):
"""Returns the hexadecimal hash for its declaration"""
if not self._name:
raise RuntimeError("dataformat has no name")
return self.storage.hash()
def validate(self, data):
"""Validates a piece of data provided by the user
......@@ -292,7 +424,9 @@ class DataFormat(object):
"""
if other.extends:
if self.name == other.extends: return True
else: return self.isparent(other.referenced[other.extends])
if self.name == other.extends:
return True
else:
return self.isparent(other.referenced[other.extends])
return False
......@@ -34,6 +34,7 @@ import simplejson
import collections
import copy
import six
import os
def _sha256(s):
......@@ -63,6 +64,12 @@ def _stringify(dictionary):
def toUserPath(username):
hash = _sha256(username)
return os.path.join(hash[0:2], hash[2:4], username)
def hash(dictionary_or_string):
if isinstance(dictionary_or_string, dict):
return _sha256(_stringify(dictionary_or_string))
......
......@@ -37,6 +37,21 @@ import six
from . import hash
def hashed_or_simple(prefix, what, path, suffix='.json'):
"""Returns a hashed path or simple path depending on where the resource is"""
username, right_bit = path.split('/', 1)
hashed_prefix = hash.toUserPath(username)
candidate = os.path.join(prefix, what, hashed_prefix, right_bit)
if os.path.exists(candidate + suffix):
return candidate
return os.path.join(prefix, what, path)
def safe_rmfile(f):
"""Safely removes a file from the disk"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment