from__future__importprint_functionfrom__future__importabsolute_importfrom__future__importdivisionimportosimportjsonfromuuidimportuuid4fromcopyimportdeepcopyimportcompasfromcompas.data.encodersimportDataEncoderfromcompas.data.encodersimportDataDecoder# ==============================================================================# If you ever feel tempted to use ABCMeta in your code: don't, just DON'T.# Assigning __metaclass__ = ABCMeta to a class causes a severe memory leak/performance# degradation on IronPython 2.7.# See these issues for more details:# - https://github.com/compas-dev/compas/issues/562# - https://github.com/compas-dev/compas/issues/649# ==============================================================================classData(object):"""Abstract base class for all COMPAS data objects. Attributes ---------- DATASCHEMA : :class:`schema.Schema` The schema of the data dict. JSONSCHEMA : dict The schema of the serialized data dict. data : dict The fundamental data describing the object. The structure of the data dict is defined by the implementing classes. """def__init__(self,name=None):self._guid=Noneself._name=Noneself._jsondefinitions=Noneself._JSONSCHEMA=Noneself._jsonvalidator=Noneifname:self.name=namedef__getstate__(self):"""Return the object data for state serialization with older pickle protocols."""return{'__dict__':self.__dict__,'dtype':self.dtype,'data':self.data}def__setstate__(self,state):"""Assign a deserialized state to the object data to support older pickle protocols."""self.__dict__.update(state['__dict__'])self.data=state['data']@propertydefDATASCHEMA(self):""":class:`schema.Schema` : The schema of the data of this object."""raiseNotImplementedError@propertydefJSONSCHEMANAME(self):raiseNotImplementedError@propertydefJSONSCHEMA(self):"""dict : The schema of the JSON representation of the data of this object."""ifnotself._JSONSCHEMA:schema_filename='{}.json'.format(self.JSONSCHEMANAME.lower())schema_path=os.path.join(os.path.dirname(__file__),'schemas',schema_filename)withopen(schema_path,'r')asfp:self._JSONSCHEMA=json.load(fp)returnself._JSONSCHEMA@propertydefjsondefinitions(self):"""dict : Reusable schema definitions."""ifnotself._jsondefinitions:schema_path=os.path.join(os.path.dirname(__file__),'schemas','compas.json')withopen(schema_path,'r')asfp:self._jsondefinitions=json.load(fp)returnself._jsondefinitions@propertydefjsonvalidator(self):""":class:`jsonschema.Draft7Validator` : JSON schema validator for draft 7."""ifnotself._jsonvalidator:fromjsonschemaimportRefResolver,Draft7Validatorresolver=RefResolver.from_schema(self.jsondefinitions)self._jsonvalidator=Draft7Validator(self.JSONSCHEMA,resolver=resolver)returnself._jsonvalidator@propertydefdtype(self):"""str : The type of the object in the form of a '2-level' import and a class name."""return'{}/{}'.format('.'.join(self.__class__.__module__.split('.')[:2]),self.__class__.__name__)@propertydefdata(self):"""dict : The representation of the object as native Python data. The structure of the data is described by the data schema. """raiseNotImplementedError@data.setterdefdata(self,data):raiseNotImplementedError@propertydefjsonstring(self):"""str: The representation of the object data in JSON format."""returncompas.json_dumps(self.data)@propertydefguid(self):"""str : The globally unique identifier of the object."""ifnotself._guid:self._guid=uuid4()returnself._guid@propertydefname(self):"""str : The name of the object. This name is not necessarily unique and can be set by the user. """ifnotself._name:self._name=self.__class__.__name__returnself._name@name.setterdefname(self,name):self._name=name@classmethoddeffrom_data(cls,data):"""Construct an object of this type from the provided data. Parameters ---------- data : dict The data dictionary. Returns ------- :class:`compas.data.Data` An object of the type of ``cls``. """obj=cls()obj.data=datareturnobjdefto_data(self):"""Convert an object to its native data representation. Returns ------- dict The data representation of the object as described by the schema. """returnself.data@classmethoddeffrom_json(cls,filepath):"""Construct an object from serialized data contained in a JSON file. Parameters ---------- filepath : path string, file-like object or URL string The path, file or URL to the file for serialization. Returns ------- :class:`compas.data.Data` An object of the type of ``cls``. """data=compas.json_load(filepath)returncls.from_data(data)defto_json(self,filepath,pretty=False):"""Serialize the data representation of an object to a JSON file. Parameters ---------- filepath : path string or file-like object The path or file-like object to the file containing the data. pretty : bool, optional If ``True`` serialize a pretty representation of the data. Default is ``False``. """compas.json_dump(self.data,filepath,pretty)@classmethoddeffrom_jsonstring(cls,string):"""Construct an object from serialized data contained in a JSON string. Parameters ---------- string : str The JSON string. Returns ------- :class:`compas.data.Data` An object of the type of ``cls``. """data=compas.json_loads(string)returncls.from_data(data)defto_jsonstring(self,pretty=False):"""Serialize the data representation of an object to a JSON string. Parameters ---------- pretty : bool, optional If ``True`` serialize a pretty representation of the data. Default is ``False``. Returns ------- str A JSON string representation of the data. """returncompas.json_dumps(self.data,pretty)defcopy(self,cls=None):"""Make an independent copy of the data object. Parameters ---------- cls : :class:`compas.data.Data`, optional The type of data object to return. Defaults to the type of the current data object. Returns ------- :class:`compas.data.Data` A separate, but identical data object. """ifnotcls:cls=type(self)returncls.from_data(deepcopy(self.data))defvalidate_data(self):"""Validate the object's data against its data schema (`self.DATASCHEMA`). Returns ------- dict The validated data. Raises ------ schema.SchemaError """importschematry:data=self.DATASCHEMA.validate(self.data)exceptschema.SchemaErrorase:print("Validation against the data schema of this object failed.")raiseereturndatadefvalidate_json(self):"""Validate the object's data against its json schema (`self.JSONSCHEMA`). Returns ------- str The validated JSON representation of the data. Raises ------ jsonschema.exceptions.ValidationError """importjsonschemajsonstring=json.dumps(self.data,cls=DataEncoder)jsondata=json.loads(jsonstring,cls=DataDecoder)try:self.jsonvalidator.validate(jsondata)exceptjsonschema.exceptions.ValidationErrorase:print("Validation against the JSON schema of this object failed.")raiseereturnjsonstring