diff --git a/README.md b/README.md index 80a46bc115ef997b8b5e5353db152b2c4558a6b0..08d4f4756adca67d016c1582d91d20c2879112b2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Python Unified Device Interface -Current stable version: 5.2.6 +Current stable version: 5.2.7 + ## Installation 1. Install the *WZL-Utilities* dependency via pip @@ -53,6 +54,9 @@ Setup your server as follows: 2. If you are not using MQTT or HTTP leave out the respective lines. ## Recent changes +5.2.7 +- improved error output for developers + 5.2.6 - bug fix - fixed serialization to RFC3339 time string @@ -171,4 +175,4 @@ Setup your server as follows: 1.4.0: - added optional ontology field to all elements -- bug fixes \ No newline at end of file +- bug fixes diff --git a/requirements.txt b/requirements.txt index 2d3ba7d83f2fa31bf34728818813e7f23166b3dd..7805da466d99b356bf3ce368aa9a70debc402bfd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,8 @@ -aiohttp @ file:///D:/bld/aiohttp_1605734736785/work +aiohttp async-timeout==3.0.1 -attrs @ file:///home/conda/feedstock_root/build_artifacts/attrs_1605083924122/work -certifi==2020.11.8 -chardet @ file:///D:/bld/chardet_1602255574834/work docstring-parser==0.7.3 -idna @ file:///home/conda/feedstock_root/build_artifacts/idna_1593328102638/work -multidict @ file:///D:/bld/multidict_1602413330828/work -nest-asyncio @ file:///home/conda/feedstock_root/build_artifacts/nest-asyncio_1605195931949/work -paho-mqtt==1.5.1 +nest-asyncio strict-rfc3339==0.7 -typing-extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1602702424206/work -wincertstore==0.2 wzl-mqtt==1.3.1 wzl-utilities==1.2.2 -yarl @ file:///D:/bld/yarl_1605429655746/work + diff --git a/setup.py b/setup.py index 4fcf85cc10d606ce8891d616376756aa63832518..09ba42cd6eb16b7834120517a4514b4c00d601fb 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup(name='wzl-udi', - version='5.2.6', + version='5.2.7', url='', author='Matthias Bodenbenner', author_email='m.bodenbenner@wzl.rwth-aachen.de', diff --git a/src/http/server.py b/src/http/server.py index 5468b66d3acd49f274073d0a95f260d8d1cae8e2..225e6684920d4153d242359910fadff5eeca1537 100644 --- a/src/http/server.py +++ b/src/http/server.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import traceback + import functools from aiohttp import web @@ -89,6 +91,7 @@ class HTTPServer(object): status = 200 logger.info('Response: {}'.format(response)) except (DeviceException, ServerException, UserException) as e: + logger.error(traceback.format_exc()) response = {'error': str(e)} status = 500 logger.error('Response: {}'.format(response)) @@ -108,6 +111,7 @@ class HTTPServer(object): status = 200 logger.info('Response: {}'.format(response)) except (DeviceException, ServerException, UserException) as e: + logger.error(traceback.format_exc()) response = {'error': str(e)} status = 500 logger.error('Response: {}'.format(response)) @@ -131,10 +135,12 @@ class HTTPServer(object): status = 200 logger.info('Response: {}'.format(response)) except ChildNotFoundException as e: + logger.error(traceback.format_exc()) response = {'error': str(e)} status = 404 logger.error('Response: {}'.format(response)) except (DeviceException, ServerException, UserException) as e: + logger.error(traceback.format_exc()) response = {'error': str(e)} status = 500 logger.error('Response: {}'.format(response)) @@ -168,10 +174,12 @@ class HTTPServer(object): status = 200 logger.info('Response: {}'.format(response)) except ReadOnlyException as e: + logger.error(traceback.format_exc()) response = {'error': str(e)} status = 403 logger.error('Response: {}'.format(response)) except InvokationException as e: + logger.error(traceback.format_exc()) response = {'error': str(e)} status = 500 logger.error('Response: {}'.format(response)) @@ -196,6 +204,7 @@ class HTTPServer(object): status = 200 logger.info('Response: {}'.format(response)) except (DeviceException, ServerException, UserException) as e: + logger.error(traceback.format_exc()) response = {'error': str(e)} status = 500 logger.error('Response: {}'.format(response)) diff --git a/src/soil/component.py b/src/soil/component.py deleted file mode 100644 index ab830e744d290bea80c532f746f3a2f3ef66a5b3..0000000000000000000000000000000000000000 --- a/src/soil/component.py +++ /dev/null @@ -1,336 +0,0 @@ -# from __future__ import annotations -import json -import os -import sys -from typing import List, Any, Union, Dict -from wzl.utilities import root_logger - -from . import docstring_parser -from .element import Element -from .error import ChildNotFoundException -from .function import Function -from .measurement import Measurement -from .parameter import Parameter -from ..utils.constants import HTTP_GET -from ..utils.error import SerialisationException, DeviceException, UserException - -logger = root_logger.get(__name__) - - -class Component(Element): - - def __init__(self, uuid: str, name: str, description: str, functions: List[Function], measurements: List[Measurement], - parameters: List[Parameter], components: List['Component'], implementation: Dict, ontology: str = None): - Element.__init__(self, uuid, name, description, ontology) - if uuid[:3] != 'COM': - raise Exception('{}: The UUID must start with COM!'.format(uuid)) - if not isinstance(functions, list): - raise Exception('{}: Given functions are not a list!'.format(uuid)) - for f in functions: - if not isinstance(f, Function): - raise Exception('{}: Given function is not of type Function!'.format(uuid)) - if not isinstance(measurements, list): - raise Exception('{}: Given measurements are not a list!'.format(uuid)) - for v in measurements: - if not isinstance(v, Measurement): - raise Exception('{}: Given measurement is not of type Variables!'.format(uuid)) - if not isinstance(parameters, list): - raise Exception('{}: Given measurements are not a list!'.format(uuid)) - for p in parameters: - if not isinstance(p, Parameter): - raise Exception('{}: Given measurement is not of type Variables!'.format(uuid)) - if not isinstance(components, list): - raise Exception('{}: Given components are not a list!'.format(uuid)) - for o in components: - if not isinstance(o, Component): - raise Exception('{}: Given component is not of type Components!'.format(uuid)) - - self._functions = functions - self._measurements = measurements - self._components = components - self._parameters = parameters - self._implementation_add = implementation['add'] if 'add' in implementation else None - self._implementation_remove = implementation['remove'] if 'remove' in implementation else None - - def __getitem__(self, item: Union[str, List[str]], method: int = HTTP_GET) -> Any: - attribute = False - if isinstance(item, str): - attribute = hasattr(self, item) - if item == "functions": - return self._functions - if item == "measurements": - return self._measurements - if item == "parameters": - return self._measurements - if item == "components": - return self._components - if item == "children": - ret = [] - everything = self._components + self._measurements + self._parameters + self._functions - for o in everything: - ret += [o.uuid] - return ret - # if the item is a list, the list contains the uuid of the descendants - if isinstance(item, list): - if len(item) > 0 and super().__getitem__('uuid', method) == item[0]: - item = item[1:] - if len(item) == 0: - return self - everything = self._components + self._measurements + self._parameters + self._functions - for o in everything: - if o.uuid == item[0]: - if len(item) == 1: - return o - else: - return o.__getitem__(item[1:], method) - raise Exception("{}: Given uuid {} is not the id of a child of the current component!".format(self.uuid, item)) - return super().__getitem__(item, method) - - def __setitem__(self, key: str, value: Any): - if key == "functions": - if not isinstance(value, list): - raise Exception('{}: Given functions are not a list!'.format(self.uuid)) - for f in value: - if not isinstance(f, Function): - raise Exception('{}: Given function is not of type Function!'.format(self.uuid)) - self._functions = value - elif key == "measurements": - if not isinstance(value, list): - raise Exception('{}: Given measurements are not a list!'.format(self.uuid)) - for v in value: - if not isinstance(v, Measurement): - raise Exception('{}: Given measurement is not of type Variable!'.format(self.uuid)) - self._measurements = value - elif key == "parameters": - if not isinstance(value, list): - raise Exception('{}: Given parameters are not a list!'.format(self.uuid)) - for v in value: - if not isinstance(v, Parameter): - raise Exception('{}: Given parameter is not of type Parameter!'.format(self.uuid)) - self._measurements = value - elif key == "components": - if not isinstance(value, list): - raise Exception('{}: Given components are not a list!'.format(self.uuid)) - for o in value: - if not isinstance(o, Component): - raise Exception('{}: Given component is not of type Component!'.format(self.uuid)) - self._components = value - else: - super().__setitem__(key, value) - - def serialize(self, keys: List[Any], method: int = HTTP_GET) -> Dict[str, Any]: - if not keys: # list is empty - keys = ['uuid', 'name', 'description', 'children', 'ontology'] - - if 'all' in keys: # serialize complete tree recursively (overrides all other keys) - dictionary = super().serialize([]) - dictionary['measurements'] = list(map(lambda x: x.serialize([]), self._measurements)) - dictionary['functions'] = list(map(lambda x: x.serialize(['all']), self._functions)) - dictionary['components'] = list(map(lambda x: x.serialize(['all']), self._components)) - dictionary['parameters'] = list(map(lambda x: x.serialize(['all']), self._parameters)) - return dictionary - - dictionary = super().serialize(keys, method) - if 'children' in keys: - everything = self._components + self._measurements + self._parameters + self._functions - dictionary['children'] = list(map(lambda x: x.serialize(['name', 'uuid']), everything)) - # if 'update' in keys: - # # TODO implement (update all children) - # pass - # # if self._implementation is not None: - # # dict['update'] = self._implementation() - # else: - # dict['children'] = list(map(lambda x: x.serialize(['value', 'uuid']), self._measurements)) - return dictionary - - @staticmethod - def merge_dictionaries(parent_dict, component_dict): - def merge_measurements(parent_list, component_list): - for measurement in parent_list: - if 'uuid' not in measurement: - raise Exception('UUID {} not given for measurement to be overwritten.'.format(measurement['uuid'])) - idx = [i for i, v in enumerate(component_list) if v['uuid'] == measurement['uuid']] - if len(idx) != 1: - raise Exception('Mismatching UUID: {}.'.format(measurement['uuid'])) - idx = idx[0] - component_list[idx].update(measurement) - return component_list - - def merge_functions(parent_list, component_list): - for function in parent_list: - if 'uuid' not in function: - raise Exception('UUID {} not given for function to be overwritten.'.format(function['uuid'])) - idx = [i for i, v in enumerate(component_list) if v['uuid'] == function['uuid']] - if len(idx) != 1: - raise Exception('Mismatching UUID: {}.'.format(function['uuid'])) - idx = idx[0] - if 'name' in function: - component_list[idx]['name'] = function['name'] - if 'description' in function: - component_list[idx]['description'] = function['description'] - if 'arguments' in function: - component_list[idx]['arguments'] = merge_measurements(function['arguments'], component_list[idx]['arguments']) - if 'returns' in function: - component_list[idx]['returns'] = merge_measurements(function['returns'], component_list[idx]['returns']) - return component_list - - # merge components, i.e. overwrite fields of "static" children dictionary with the "dynamic" fields of the parents dictionary - uuid = parent_dict['uuid'] - component_dict['uuid'] = uuid - if 'name' in parent_dict: - component_dict['name'] = parent_dict['name'] - if 'description' in parent_dict: - component_dict['description'] = parent_dict['description'] - if 'measurements' in parent_dict: - component_dict['measurements'] = merge_measurements(parent_dict['measurements'], component_dict['measurements']) - if 'parameters' in parent_dict: - component_dict['paramters'] = merge_measurements(parent_dict['parameters'], component_dict['parameters']) - if 'functions' in parent_dict: - component_dict['functions'] = merge_functions(parent_dict['functions'], component_dict['functions']) - if 'components' in parent_dict: - for obj in parent_dict['components']: - index = [i for i, o in enumerate(component_dict['components']) if o['uuid'] == obj['uuid']] - if len(index) != 1: - raise Exception('Mismatching UUID: {}.'.format(obj['uuid'])) - index = index[0] - component_dict['components'][index] = Component.merge_dictionaries(obj, component_dict['components'][index]) - return component_dict - - @staticmethod - def deserialize(dictionary, mapping=None, filepath=''): - if 'uuid' not in dictionary: - raise SerialisationException('The component can not be deserialized. UUID is missing!') - uuid = dictionary['uuid'] - if uuid[:3] != 'COM': - raise SerialisationException( - 'The component can not be deserialized. The UUID must start with COM, but actually starts with {}!'.format(uuid[:3])) - if 'file' in dictionary: - try: - with open(os.path.normpath(os.path.join(filepath, dictionary['file']))) as file: - component_dict = json.load(file) - dictionary = Component.merge_dictionaries(dictionary, component_dict) - except Exception as e: - raise SerialisationException('{}: The component can not be deserialized. Provided JSON-file can not be parsed! {}'.format(uuid, e)) - if 'name' not in dictionary: - raise SerialisationException('{}: The component can not be deserialized. Name is missing!'.format(uuid)) - if 'description' not in dictionary: - raise SerialisationException('{}: The component can not be deserialized. Description is missing!'.format(uuid)) - if 'measurements' not in dictionary: - raise SerialisationException('{}: The component can not be deserialized. List of measurements is missing!'.format(uuid)) - if 'parameters' not in dictionary: - raise SerialisationException('{}: The component can not be deserialized. List of parameters is missing!'.format(uuid)) - if 'functions' not in dictionary: - raise SerialisationException('{}: The component can not be deserialized. List of functions is missing!'.format(uuid)) - if 'components' not in dictionary: - raise SerialisationException('{}: The component can not be deserialized. List of components is missing!'.format(uuid)) - - try: - measurements = [] - for var in dictionary['measurements']: - if mapping is not None: - submapping = mapping[var["uuid"]] if var['uuid'] in mapping else None - measurements += [Measurement.deserialize(var, submapping)] - else: - measurements += [Measurement.deserialize(var)] - except Exception as e: - raise SerialisationException('{}: A measurement of the component can not be deserialized. {}'.format(uuid, e)) - try: - parameters = [] - for par in dictionary['parameters']: - if mapping is not None: - submapping = mapping[par["uuid"]] if par['uuid'] in mapping else None - parameters += [Parameter.deserialize(par, submapping)] - else: - parameters += [Parameter.deserialize(par)] - except Exception as e: - raise SerialisationException('{}: A parameter of the component can not be deserialized. {}'.format(uuid, e)) - try: - functions = [] - for func in dictionary['functions']: - if mapping is not None: - submapping = mapping[func["uuid"]] if func['uuid'] in mapping else None - functions += [Function.deserialize(func, submapping)] - else: - functions += [Function.deserialize(func)] - except Exception as e: - raise SerialisationException('{}: A function of the component can not be deserialized. {}'.format(uuid, e)) - try: - components = [] - for obj in dictionary['components']: - if mapping is not None: - submapping = mapping[obj["uuid"]] if obj['uuid'] in mapping else None - components += [Component.deserialize(obj, submapping, filepath)] - else: - components += [Component.deserialize(obj, filepath=filepath)] - except Exception as e: - raise SerialisationException('{}: An component of the component can not be deserialized. {}'.format(uuid, e)) - try: - ontology = dictionary['ontology'] if 'ontology' in dictionary else None - return Component(dictionary['uuid'], dictionary['name'], dictionary['description'], functions, measurements, parameters, components, mapping, - ontology) - except Exception as e: - raise SerialisationException('{}: The component can not be deserialized. {}'.format(uuid, e)) - - def write(self, filename: str): - if filename[-5:] != ".json": - raise Exception('{} is not a json file!'.format(filename)) - - model_dict = self.serialize(['all']) - - f = open(filename, "w") - f.write(json.dumps(model_dict)) - f.close() - - def update(self, element: Union['Component', Function, Measurement, Parameter]): - if isinstance(element, Component): - for i, o in enumerate(self._components): - if o.uuid == element.uuid: - self._components[i] = element - return - # self._components.append(element) - else: - raise Exception("Wrong type updating element on existing model!") - - def add(self, uuid: str, class_name: str, json_file: str, *args, **kwargs): - if uuid[:3] == 'COM': - if uuid not in [o.uuid for o in self._components]: - try: - __import__(class_name) - implementation = getattr(sys.modules[class_name], class_name)(*args, **kwargs) - mapping = docstring_parser.parse_docstrings_for_soil(implementation) - self._components += [Component.load(json_file, mapping)] - if self._implementation_add is not None: - self._implementation_add(implementation) - except Exception as e: - raise DeviceException('Can not add component with UUID {}. {}'.format(uuid, e), predecessor=e) - else: - raise UserException('Component has already a child with UUID {}.'.format(uuid)) - else: - raise UserException('UUID {} is not of the UUID of an component.'.format(uuid)) - - def remove(self, uuid: str, *args, **kwargs): - for o in self._components: - if o.uuid == uuid: - if self._implementation_remove is not None: - try: - self._implementation_remove(*args, **kwargs) - except Exception as e: - raise DeviceException(str(e), predecessor=e) - self._components.remove(o) - return - raise ChildNotFoundException('{}: Child {} not found!'.format(self.uuid, uuid)) - - @staticmethod - def load(file: Union[str, dict], implementation: Any) -> 'Component': - if isinstance(file, str): - if not os.path.isfile(file): - raise Exception('There is no file named {}!'.format(file)) - if file[-5:] != ".json": - raise Exception('{} is not a json file!'.format(file)) - with open(file, 'r') as f: - model_dict = json.load(f) - return Component.deserialize(model_dict, implementation, os.path.dirname(file)) - elif isinstance(file, dict): - return Component.deserialize(file, implementation) - else: - raise Exception('Given file is not a name of a json-file nor a json-like dictionary.') diff --git a/src/soil/docstring_parser.py b/src/soil/docstring_parser.py index b776036427483789379559e2a5c3880e357145d3..39c48aa6a5a13ddc33f0db02adf8b51ccffc613d 100644 --- a/src/soil/docstring_parser.py +++ b/src/soil/docstring_parser.py @@ -60,7 +60,6 @@ def parse_docstrings_for_mqtt(implementation, parent_url="", *args, **kwargs): child_schedule = parse_children(parse_docstrings_for_mqtt, parent_url, implementation) for key in child_schedule: schedule += [*child_schedule[key]] - print(schedule) # parse functions and setters/getters of variables and parameters object_dict = implementation.__class__.__dict__ diff --git a/src/soil/error.py b/src/soil/error.py index 26f9f30ff5dc2901336110f9d0a5c7386f9da13c..df67dad4921f8a40a05ea92148c9ce7ebb1d8735 100644 --- a/src/soil/error.py +++ b/src/soil/error.py @@ -4,25 +4,43 @@ from ..utils.error import BasicException, DeviceException, UserException class TypeException(DeviceException): def __init__(self, description): - BasicException.__init__(self, description) + DeviceException.__init__(self, description) class RangeException(DeviceException): def __init__(self, description): - BasicException.__init__(self, description) + DeviceException.__init__(self, description) class DimensionException(DeviceException): def __init__(self, description): - BasicException.__init__(self, description) + DeviceException.__init__(self, description) + + +class InvalidModelException(UserException): + + def __init__(self, description): + UserException.__init__(self, description) + + +class InvalidMappingException(UserException): + + def __init__(self, description): + UserException.__init__(self, description) class ChildNotFoundException(DeviceException): def __init__(self, description): - BasicException.__init__(self, description) + DeviceException.__init__(self, description) + + +class AmbiguousUUIDException(UserException): + + def __init__(self, description): + UserException.__init__(self, description) class ReadOnlyException(UserException): @@ -30,18 +48,18 @@ class ReadOnlyException(UserException): def __init__(self, uuid, name, description=None): if description is None: description = 'The parameter "{}" with UUID "{}" is a read-only parameter.'.format(name, uuid) - BasicException.__init__(self, description) + UserException.__init__(self, description) class NotImplementedException(UserException): def __init__(self, uuid, name, description=None): if description is None: description = 'The function "{}" with UUID "{}" is not implemented.'.format(name, uuid) - BasicException.__init__(self, description) + UserException.__init__(self, description) class InvokationException(DeviceException): def __init__(self, uuid, name, description=None): if description is None: description = 'Error when invoking "{}" with UUID "{}".'.format(name, uuid) - BasicException.__init__(self, description) \ No newline at end of file + DeviceException.__init__(self, description) diff --git a/src/soil/measurement.py b/src/soil/measurement.py deleted file mode 100644 index c38f79210e81eced6e27f6b63d673011e04af087..0000000000000000000000000000000000000000 --- a/src/soil/measurement.py +++ /dev/null @@ -1,141 +0,0 @@ -from deprecated import deprecated -from typing import Dict -from wzl.utilities import root_logger - -from ..utils.constants import HTTP_GET -from ..utils.error import SerialisationException -from .figure import Figure - -logger = root_logger.get(__name__) - - -class Measurement(Figure): - - def __init__(self, uuid, name, description, datatype, dimension, range, getter, unit, nonce=None, ontology: str = None): - Figure.__init__(self, uuid, name, description, datatype, dimension, range, None, getter, ontology) - if uuid[:3] != 'MEA': - raise Exception('{}: The UUID must start with MEA!'.format(uuid)) - self._unit = unit - self._covariance = None # TODO init - self._uncertainty = None # TODO init - self._timestamp = None - self._nonce = nonce - - @property - def unit(self): - return self._unit - - @property - def covariance(self): - return self._covariance - - @property - def uncertainty(self): - return self._uncertainty - - @property - def timestamp(self): - return self._timestamp - - @property - def nonce(self): - return self._nonce - - def __getitem__(self, item: str, method=HTTP_GET): - """ - Getter-Method. - According to the given key the method returns the value of the corresponding attribute. - :param item: name of the attribute. Provided as string without leading underscores. - :param method: ??? - :return: the value of the attribute indicated by 'item'. - """ - if item == "unit": - return self._unit - if item == 'nonce': - return self._nonce - if item == 'covariance': - return self._covariance - if item == 'uncertainty': - return self._uncertainty - if item == 'timestamp': - return self._timestamp - if item == []: - return self - return super().__getitem__(item, method) - - def __setitem__(self, key: str, value): - """ - Setter - Method - If key is "value" datatype, dimension and range is checked for correctness. - :param key: sets the value of the attribute with name 'item' to the provided value. - :param value: value to be set - """ - if key in ['value', 'timestamp', 'covariance', 'uncertainty']: - raise KeyError('The {} attribute of a measurement can not be set manually!'.format(key)) - elif key == "nonce": - self._nonce = self._nonce - elif key == 'unit': - self._unit = self._unit - else: - super().__setitem__(key, value) - - def serialize(self, keys: [str], method=HTTP_GET): - """ - Serializes an object of type Measurement into a JSON-like dictionary. - :param keys: All attributes given in the "keys" array are serialized. - :param method: ??? - :return: a dictionary having all "keys" as keys and the values of the corresponding attributes as value. - """ - # list is empty provide all attributes of the default-serialization - if not keys: - keys = ['uuid', 'name', 'description', 'datatype', 'value', 'dimension', 'range', 'timestamp', 'nonce', 'covariance', 'uncertainty', - 'unit', 'ontology'] - # TODO temporary workaround, forces the server to always return the timestamp if the value is queried - if 'value' in keys and 'timestamp' not in keys: - keys += ['timestamp'] - dictionary = {} - # get all attribute values - for key in keys: - value = self.__getitem__(key, method) - # in case of timestamp convert into RFC3339 string - if key == 'timestamp' or (key == 'value' and self._datatype == 'time'): - value = value.isoformat() + 'Z' if value is not None else "" - dictionary[key] = value - return dictionary - - @staticmethod - def deserialize(dictionary: Dict, implementation=None): - """ - Takes a JSON-like dictionary, parses it, performs a complete correctness check and returns an object of type Figure with the - values provided in the dictionary, if dictionary is a valid serialization of a Figure. - :param dictionary: serialized measurement - :param implementation: implementation wrapper object, - :return: an object of type Figure - """ - # check if all required attributes are present - if 'uuid' not in dictionary: - raise SerialisationException('The measurement can not be deserialized. UUID is missing!') - uuid = dictionary['uuid'] - if uuid[:3] != 'MEA': - raise SerialisationException( - 'The Measurement can not be deserialized. The UUID must start with MEA, but actually starts with {}!'.format(uuid[:3])) - if 'name' not in dictionary: - raise SerialisationException('{}: The measurement can not be deserialized. Name is missing!'.format(uuid)) - if 'description' not in dictionary: - raise SerialisationException('{}: The measurement can not be deserialized. Description is missing!'.format(uuid)) - if 'datatype' not in dictionary: - raise SerialisationException('{}: The measurement can not be deserialized. Datatype is missing!'.format(uuid)) - if 'dimension' not in dictionary: - raise SerialisationException('{}: The measurement can not be deserialized. Dimension is missing!'.format(uuid)) - if 'value' not in dictionary: - raise SerialisationException('{}: The measurement can not be deserialized. Value is missing!'.format(uuid)) - if 'range' not in dictionary: - raise SerialisationException('{}: The measurement can not be deserialized. Range is missing!'.format(uuid)) - if 'unit' not in dictionary: - raise SerialisationException('{}: The measurement can not be deserialized. Unit is missing!'.format(uuid)) - try: - ontology = dictionary['ontology'] if 'ontology' in dictionary else None - return Measurement(dictionary['uuid'], dictionary['name'], dictionary['description'], dictionary['datatype'], dictionary['dimension'], - dictionary['range'], implementation, dictionary['unit'], ontology) - except Exception as e: - raise SerialisationException('{}: The measurement can not be deserialized. {}'.format(uuid, e))