class EnergySystemHandler: """Class to handle (load, read, and update) an ESDL Energy System""" def __init__(self, name=None): # create a resourceSet that hold the contents of the esdl.ecore model and the instances we use/create self.rset = ResourceSet() # Assign files with the .esdl extension to the XMLResource instead of default XMI self.rset.resource_factory['esdl'] = lambda uri: XMLResource(uri) # Read the esdl.ecore from the tmp folder esdl_model_resource = self.rset.get_resource( URI('tmp/esdl/esdl.ecore')) esdl_model = esdl_model_resource.contents[0] # print('Namespace: {}'.format(esdl_model.nsURI)) self.rset.metamodel_registry[esdl_model.nsURI] = esdl_model # Create a dynamic model from the loaded esdl.ecore model, which we can use to build Energy Systems self.esdl = DynamicEPackage(esdl_model) # fix python buildin 'from' that is also used in ProfileElement as attribute # use 'start' instead of 'from' when using a ProfileElement alias('start', self.esdl.ProfileElement.findEStructuralFeature('from')) # have a nice __repr__ for some ESDL classes when printing ESDL objects (includes all Assets and EnergyAssets) self.esdl.Item.python_class.__repr__ = lambda x: '{}: ({})'.format( x.name, EnergySystemHandler.attr_to_dict(x)) self.esdl.Carrier.python_class.__repr__ = lambda x: '{}: ({})'.format( x.name, EnergySystemHandler.attr_to_dict(x)) self.esdl.Geometry.python_class.__repr__ = lambda x: '{}: ({})'.format( x.name, EnergySystemHandler.attr_to_dict(x)) self.esdl.QuantityAndUnitType.python_class.__repr__ = lambda x: '{}: ({})'.format( x.id, EnergySystemHandler.attr_to_dict(x)) self.esdl.QuantityAndUnitReference.python_class.__repr__ = lambda x: '{}: ({})'.format( 'QuantityAndUnitReference', EnergySystemHandler.attr_to_dict(x)) self.esdl.KPI.python_class.__repr__ = lambda x: '{}: ({})'.format( x.name, EnergySystemHandler.attr_to_dict(x)) self.esdl.ProfileElement.python_class.__repr__ = lambda x: 'ProfileElement ({})'.format( EnergySystemHandler.attr_to_dict(x)) if name: self.name = name self.load_energy_system(name) # Creates a dict of all the attributes of an ESDL object @staticmethod def attr_to_dict(esdl_object): d = dict() d['esdlType'] = esdl_object.eClass.name for attr in dir(esdl_object): attr_value = esdl_object.eGet(attr) if attr_value is not None: d[attr] = attr_value return d # Creates a uuid: useful for generating unique IDs @staticmethod def generate_uuid(): return str(uuid.uuid4()) def load_energy_system(self, name): # load the ESDL file self.resource = self.rset.get_resource(URI(name)) self.es = self.resource.contents[0] # Add Energy System Information def add_energy_system_information(self): esi = self.esdl.EnergySystemInformation(id='energy_system_information') self.es.energySystemInformation = esi def add_data_source(self, name, description): data_source = self.esdl.DataSource(id='data_source', name=name, description=description) self.es.dataSource = data_source # Add energy system information to the energy system if it is not there yet # Energy System information can be used to globally define the quantity and units of this system, # instead of defining them manually per KPI in each area: this fosters reuse (but is not necessary) def get_quantity_and_units(self): q_and_u = None if self.get_by_id('energy_system_information') is None: self.add_energy_system_information() q_and_u = self.esdl.QuantityAndUnits(id='quantity_and_units') self.es.energySystemInformation.quantityAndUnits = q_and_u else: q_and_u = self.get_by_id('quantity_and_units') return q_and_u # Add Measures object to Energy System def add_measures(self): # Create new Measures object measures = self.esdl.Measures(id='measures') self.es.instance[0].area.measures = measures # Append measure to Measures object def append_measure(self, measure): self.es.instance[0].area.measures.measure.append(measure) # Append asset measure to Measures object def append_asset_to_measure(self, measure, asset): measure.asset.append(asset) self.es.instance[0].area.measures.measure.append(measure) # Add KPIs object to Energy System def add_kpis(self): # create new KPIs object kpis = self.esdl.KPIs(id='kpis', description='KPIs') self.es.instance[0].area.KPIs = kpis # Create new KPI object def create_kpi(self, kpi_type, kpi_id, name, q_and_u): return getattr(self.esdl, kpi_type)(id=kpi_id, name=name, quantityAndUnit=q_and_u) # Add KPI to KPIs object def add_kpi(self, kpi): self.es.instance[0].area.KPIs.kpi.append(kpi) # Get a list of assets of a specific ESDL type in the main instance's area # def get_assets_of_type(self, esdl_type): # assets = [] # # for current_asset in self.es.instance[0].area.asset: # if isinstance(current_asset, esdl_type): # assets.append(current_asset) # return assets # Get a list of assets of a specific ESDL type in the specified area or asset def get_assets_of_type(self, area, esdl_type): assets = [] for current_asset in area.asset: if isinstance(current_asset, esdl_type): assets.append(current_asset) return assets # Get a list of assets of a specific ESDL type in the specified area or asset # filtered by a specified attribute-value combination def get_assets_of_type_and_attribute_value(self, area, esdl_type, attribute, value): assets = [] for current_asset in area.asset: if isinstance(current_asset, esdl_type): if str(getattr(current_asset, attribute)) == value: assets.append(current_asset) return assets # Get a list of potentials of a specific ESDL type in the main instance's area def get_potentials_of_type(self, esdl_type): potentials = [] for current_potential in self.es.instance[0].area.potential: if isinstance(current_potential, esdl_type): potentials.append(current_potential) return potentials # returns a generator of all assets or potentials of a specific type. Not only the ones defined in the main Instance's Area # e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object # this function returns all of them at once def get_all_instances_of_type(self, esdl_type): return esdl_type.allInstances() # Using this function you can query for objects by ID # After loading an ESDL-file, all objects that have an ID defines are stored in resource.uuid_dict automatically # Note: If you add things later to the resource, it won't be added automatically to this dictionary though. # Use get_by_id_slow() for that def get_by_id(self, id): if id in self.resource.uuid_dict: return self.resource.uuid_dict[id] else: return None # This function iterates over all the contents of the Energy System and is much slower than get_by_id() def get_by_id_slow(self, id): for child in self.es.eAllContents(): if hasattr(child, 'id'): if child.id == id: return child # create a readable list of the attributes of an ESDL class def get_asset_attribute(self, esdl_type, attribute): asset_data = [] for current_asset in self.es.instance[0].area.asset: if isinstance(current_asset, esdl_type): asset_data.append({ 'name': current_asset.name, # name 'attribute': { 'key': attribute, 'value': getattr(current_asset, attribute) } }) return asset_data # create a readable list of the attributes of an ESDL class # (scoped to a specific area) def get_asset_attribute(self, area, esdl_type, attribute): asset_data = [] for current_asset in area.asset: if isinstance(current_asset, esdl_type): asset_data.append({ 'name': current_asset.name, # name 'attribute': { 'key': attribute, 'value': getattr(current_asset, attribute) } }) return asset_data # returns a specific KPI by id, see also get_by_id for a faster method def get_kpi_by_id(self, id): for kpi in self.es.instance[0].area.KPIs.kpi: if kpi.id == id: return kpi # returns a specific KPI by name def get_kpi_by_name(self, name): for kpi in self.es.instance[0].area.KPIs.kpi: if kpi.name == name: return kpi # save the resource def save(self, filename): uri = URI(filename) fileresource = self.rset.create_resource(uri) # add the current energy system fileresource.append(self.es) # save the resource fileresource.save() # return the resource return fileresource # get the energy system as a XML String # does not change the 'active' resource # so save() will still save as a file def get_as_string(self): # to use strings as resources, we simulate a string as being a file uri = StringURI('tmp/anyname.esdl') # create the string resource stringresource = self.rset.create_resource(uri) # add the current energy system stringresource.append(self.es) # save the resource stringresource.save() # remove the temporary resource in the resource set self.rset.remove_resource(stringresource) # return the string return uri.getvalue() def get_as_stream(self): # to use strings as resources, we simulate a string as being a file uri = StringURI('tmp/anyname.esdl') # create the string resource stringresource = self.rset.create_resource(uri) # add the current energy system stringresource.append(self.es) # save the resource stringresource.save() # remove the temporary resource in the resource set self.rset.remove_resource(stringresource) # return the string return uri.get_stream() # load an EnergySystem from a string (using UTF-8 encoding) def load_from_string(self, string): uri = StringURI('loadfromstring', string) # this overrides the current loaded resource self.resource = self.rset.create_resource(uri) self.resource.load() self.es = self.resource.contents[0] return self.es
class EnergySystemHandler: """Class to handle (load, read, and update) an ESDL Energy System""" # TODO: CAN WE USE THE THING FROM THE VENDOR MODULE? # TODO: extract this to a simple module (NO CLASS) and just import the (self.)esdl # somewhere and define all the lookup there - because the lookup does not need # this whole class right?? just the self.esdl and that has nothing to do with the # actual esdl?? def __init__(self, name=None): # create a resourceSet that hold the contents of the esdl.ecore model and the # instances we use/create self.rset = ResourceSet() # Assign files with the .esdl extension to the XMLResource instead of default XMI self.rset.resource_factory['esdl'] = lambda uri: XMLResource(uri) # Read the esdl.ecore from the tmp folder esdl_model_resource = self.rset.get_resource(URI('vendor/esdl/esdl.ecore')) esdl_model = esdl_model_resource.contents[0] # print('Namespace: {}'.format(esdl_model.nsURI)) self.rset.metamodel_registry[esdl_model.nsURI] = esdl_model # Create a dynamic model from the loaded esdl.ecore model, which we can use to # build Energy Systems self.esdl = DynamicEPackage(esdl_model) # fix python buildin 'from' that is also used in ProfileElement as attribute # use 'start' instead of 'from' when using a ProfileElement alias('start', self.esdl.ProfileElement.findEStructuralFeature('from')) # have a nice __repr__ for some ESDL classes when printing ESDL objects # (includes all Assets and EnergyAssets) # self.esdl.Item.python_class.__repr__ = lambda x: format_repr(x.name, x) self.esdl.Carrier.python_class.__repr__ = lambda x: format_repr(x.name, x) self.esdl.Geometry.python_class.__repr__ = lambda x: format_repr(x.name, x) self.esdl.QuantityAndUnitType.python_class.__repr__ = lambda x: format_repr(x.id, x) self.esdl.QuantityAndUnitReference.python_class.__repr__ = lambda x: format_repr('QuantityAndUnitReference', x) self.esdl.KPI.python_class.__repr__ = lambda x: format_repr(x.name, x) self.esdl.ProfileElement.python_class.__repr__ = lambda x: format_repr('ProfileElement', x) if name: self.name = name self.load_energy_system(name) @staticmethod def generate_uuid(): ''' Creates a uuid: useful for generating unique IDs ''' return str(uuid.uuid4()) def load_energy_system(self, name): ''' Load an ESDL file ''' self.resource = self.rset.get_resource(URI(name)) self.es = self.resource.contents[0] def add_data_source(self, name, description): ''' Adds a data_source to the ESDL containing a name and description''' data_source = self.esdl.DataSource(id='data_source', name=name, description=description) self.es.dataSource = data_source def get_quantity_and_units(self): ''' Returns quantity and units Add energy system information to the energy system if it is not there yet Energy System information can be used to globally define the quantity and units of this system, instead of defining them manually per KPI in each area: this fosters reuse (but is not necessary) ''' if not self.get_by_id('energy_system_information') is None: return self.get_by_id('quantity_and_units') self.__add_energy_system_information() q_and_u = self.esdl.QuantityAndUnits(id='quantity_and_units') self.es.energySystemInformation.quantityAndUnits = q_and_u return q_and_u def __add_energy_system_information(self): '''Add Energy System Information''' esi = self.esdl.EnergySystemInformation(id='energy_system_information') self.es.energySystemInformation = esi def add_measures(self): ''' Add Measures object to Energy System ''' # Create new Measures object measures = self.esdl.Measures(id='measures') self.es.instance[0].area.measures = measures def append_measure(self, measure): ''' Append measure to Measures object ''' self.es.instance[0].area.measures.measure.append(measure) def append_asset_to_measure(self, measure, asset): ''' Append asset measure to Measures object ''' measure.asset.append(asset) self.es.instance[0].area.measures.measure.append(measure) ######## KPIs ######## def add_kpis(self): ''' Add KPIs object to Energy System ''' # create new KPIs object kpis = self.esdl.KPIs(id='kpis', description='KPIs') self.es.instance[0].area.KPIs = kpis def create_kpi(self, kpi_type, kpi_id, name, q_and_u): ''' Create new KPI object ''' return getattr(self.esdl, kpi_type)( id=kpi_id, name=name, quantityAndUnit=q_and_u ) def add_kpi(self, kpi): ''' Add KPI to KPIs object ''' self.es.instance[0].area.KPIs.kpi.append(kpi) def get_kpi_by_id(self, kpi_id): ''' Returns a specific KPI by id, see also get_by_id for a faster method ''' for kpi in self.es.instance[0].area.KPIs.kpi: if kpi.id == kpi_id: return kpi return None def get_kpi_by_name(self, name): ''' Returns a specific KPI by name ''' for kpi in self.es.instance[0].area.KPIs.kpi: if kpi.name == name: return kpi return None ######### LOOKUP ########## def get_by_id(self, object_id): ''' Using this function you can query for objects by ID After loading an ESDL-file, all objects that have an ID defines are stored in resource.uuid_dict automatically Note: If you add things later to the resource, it won't be added automatically to this dictionary though. Use get_by_id_slow() for that ''' if object_id in self.resource.uuid_dict: return self.resource.uuid_dict[object_id] return None def get_by_id_slow(self, object_id): ''' This function iterates over all the contents of the Energy System and is much slower than get_by_id() ''' for child in self.es.eAllContents(): if hasattr(child, 'id') and child.id == object_id: return child return None def get_assets_of_type(self, esdl_type, area=None): '''Get a list of assets of a specific ESDL type in the specified area or asset''' assets = area.asset if not area is None else self.es.instance[0].area.asset esdl_asset = getattr(self.esdl, esdl_type) return [asset for asset in assets if isinstance(asset, esdl_asset)] def has_assets_of_type(self, esdl_type, area=None): ''' Boolean, see get_assets_of_type ''' assets = area.asset if not area is None else self.es.instance[0].area.asset try: next((asset for asset in assets if isinstance(asset, getattr(self.esdl, esdl_type)))) return True except StopIteration: return False def get_assets_of_type_and_attribute_value(self, esdl_type, area, attr, val): ''' Returns a list of assets of a specific ESDL type in the specified area or asset filtered by a specified attribute-value combination Params: esdl_type (str): The type of asset area (esdl.Area): The area that contains the assets to be filtered attr (str): The attribute that should be evaluated val (str): The value that the attribute should have Returns: list[esdl.Asset] ''' esdl_asset = getattr(self.esdl, esdl_type) return [asset for asset in area.asset if isinstance(asset, esdl_asset) and str(getattr(asset, attr)) == val] def has_assets_of_type_and_attribute_value(self, esdl_type, area, attr, val): ''' Works like get_of_type_and_attribute_value, but only checks if the first item can be generated Params: esdl_type (str): The type of asset area (esdl.Area): The area that contains the assets to be filtered attr (str): The attribute that should be evaluated val (str): The value that the attribute should have Returns: bool ''' try: next((asset for asset in area.asset if isinstance(asset, getattr(self.esdl, esdl_type)) and str(getattr(asset, attr)) == val)) return True except StopIteration: return False def get_potentials_of_type(self, esdl_type): ''' Get a list of potentials of a specific ESDL type in the main instance's area ''' potentials = [] for current_potential in self.es.instance[0].area.potential: if isinstance(current_potential, esdl_type): potentials.append(current_potential) return potentials def get_all_instances_of_type(self, esdl_type): ''' Returns a generator of all assets or potentials of a specific type. Not only the ones defined in the main Instance's Area e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object this function returns all of them at once. Params: esdl_type (str): The type of asset Returns: generator of assets or potentials ''' yield from getattr(self.esdl, esdl_type).allInstances(resources=(self.resource,)) def get_all_instances_of_type_and_attribute_value(self, esdl_type, attr, val): ''' Returns a generator of all assets or potentials of a specific type. Not only the ones defined in the main Instance's Area e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object this function returns all of them at once. The assets are then filtered for a specific attribute-value combination Params: esdl_type (str): The type of asset attr (str): The attribute that should be evaluated val (str): The value that the attribute should have Returns: generator of assets or potentials ''' yield from (inst for inst in self.get_all_instances_of_type(esdl_type) if str(getattr(inst, attr)) == val) def get_all_instances_of_type_and_sector(self, esdl_type, sector_id): ''' Returns a generator of all assets or potentials of a specific type. Not only the ones defined in the main Instance's Area e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object this function returns all of them at once. The assets are then filtered for a specific combination on an attribute and it's ID. Params: esdl_type (str): The type of asset sector_id (str | list[str]): The value of the sectors id, e.g. REF for Refineries Returns: generator of assets or potentials ''' yield from (inst for inst in self.get_all_instances_of_type(esdl_type) if self.in_sector(inst, sector_id)) def get_all_instances_of_type_and_carrier(self, esdl_type, carrier_id): ''' Returns a generator of all assets or potentials of a specific type. Not only the ones defined in the main Instance's Area e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object this function returns all of them at once. The assets are then filtered for having the specified carrier as input. Params: esdl_type (str): The type of asset carrier_id (str | list[str]): The value of the carriers id, e.g. HTLH for network gas Returns: generator of assets or potentials ''' yield from (inst for inst in self.get_all_instances_of_type(esdl_type) if self.has_carrier(inst, carrier_id)) def get_all_instances_of_type_carrier_and_sector(self, esdl_type, sector_id, carrier_id): ''' Returns a generator of all assets or potentials of a specific type. Not only the ones defined in the main Instance's Area e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object this function returns all of them at once. The assets are then filtered for given sectors and carriers. Params: esdl_type (str): The type of asset sector_id (str | list[str]): The value of the sectors id, e.g. REF for Refineries carrier_id (str | list[str]): The value of the carriers id, e.g. HTLH for network gas Returns: generator of assets or potentials ''' yield from (inst for inst in self.get_all_instances_of_type(esdl_type) if self.in_sector(inst, sector_id) and self.has_carrier(inst, carrier_id)) def has_carrier(self, asset, carrier_id): '''Carrier id may also be a list''' for port in asset.port: if not isinstance(port, self.esdl.InPort): continue if port.carrier.id == carrier_id: return True if isinstance(carrier_id, list) and port.carrier.id in carrier_id: return True return False def in_sector(self, asset, sector_id): ''' Returns Boolean depending on if the asset is in the sector or not. Also checks if the assets parents have a sector set, if 'sector' is not an attribute of the asset directly. Params: asset (pyecore.ecore.Object): The asset that is to be checked. sector_id (str | list[str]): The id of the sector, e.g. REF for Refineries. Returns: bool ''' if asset.sector and equal_or_in(asset.sector.id, sector_id): return True for port in asset.port: if not isinstance(port, self.esdl.OutPort): continue for connection in port.connectedTo: if not getattr(connection.energyasset, 'sector', None): continue if equal_or_in(connection.energyasset.sector.id, sector_id): return True return False def get_asset_attribute(self, esdl_type, attr, area=None): ''' Create a readable list of the attributes of an ESDL class Scoped to a specific area, if one is given Returns: list[dict] with formatted assets ''' assets = area.asset if not area is None else self.es.instance[0].area.asset return [self.__format_asset(ass, attr) for ass in assets if isinstance(ass, esdl_type)] def __format_asset(self, current_asset, attribute): return { 'name': current_asset.name, # name 'attribute': { 'key': attribute, 'value': getattr(current_asset, attribute) } } ######### LOAD & SAVE ########## def save(self, filename): ''' Saves the energy system to a file ''' uri = URI(filename) fileresource = self.rset.create_resource(uri) # add the current energy system fileresource.append(self.es) # save the resource fileresource.save() # return the resource return fileresource def get_as_string(self): ''' Returns the current energy system as an XML string Note: does not change the 'active' resource so save() will still save as a file ''' # to use strings as resources, we simulate a string as being a file uri = StringURI('tmp/anyname.esdl') # create the string resource stringresource = self.rset.create_resource(uri) # add the current energy system stringresource.append(self.es) # save the resource stringresource.save() # remove the temporary resource in the resource set self.rset.remove_resource(stringresource) # return the string return uri.getvalue() def get_as_stream(self): ''' Returns the current energy system as a BytesIO stream ''' # to use strings as resources, we simulate a string as being a file uri = StringURI('tmp/anyname.esdl') # create the string resource stringresource = self.rset.create_resource(uri) # add the current energy system stringresource.append(self.es) # save the resource stringresource.save() # remove the temporary resource in the resource set self.rset.remove_resource(stringresource) # return the string return uri.get_stream() @classmethod def from_string(cls, esdl_string): ''' Create a new EnergySystemHandler based on an EnergySystem esdl_string (using UTF-8 encoding) ''' try: handler = EnergySystemHandler() handler.resource = handler.rset.create_resource( StringURI('loadfromstring', esdl_string) ) handler.resource.load() handler.es = handler.resource.contents[0] return handler except Exception as exception: raise EnergysystemParseError('ESDL could not be parsed') from exception
class EnergySystemHandler: def __init__(self, energy_system=None): if energy_system is not None: self.energy_system = energy_system self.resource = None self.rset = ResourceSet() self.esid_uri_dict = {} self._set_resource_factories() # fix python builtin 'from' that is also used in ProfileElement as attribute # use 'start' instead of 'from' when using a ProfileElement # and make sure that it is serialized back as 'from' instead of 'from_' esdl.ProfileElement.from_.name = 'from' setattr(esdl.ProfileElement, 'from', esdl.ProfileElement.from_) alias('start', esdl.ProfileElement.from_) # also for FromToIntItem esdl.FromToIntItem.from_.name = 'from' setattr(esdl.FromToIntItem, 'from', esdl.FromToIntItem.from_) alias('start', esdl.FromToIntItem.from_) # also for FromToDoubleItem esdl.FromToDoubleItem.from_.name = 'from' setattr(esdl.FromToDoubleItem, 'from', esdl.FromToDoubleItem.from_) alias('start', esdl.FromToDoubleItem.from_) # add support for cloning of EObjects and coppy.copy() setattr(EObject, '__copy__', support_functions.clone) setattr(EObject, 'clone', support_functions.clone) # add support for deepcopying EObjects and copy.deepcopy() setattr(EObject, '__deepcopy__', support_functions.deepcopy) setattr(EObject, 'deepcopy', support_functions.deepcopy) # have a nice __repr__ for some ESDL classes when printing ESDL objects (includes all Assets and EnergyAssets) esdl.EnergySystem.__repr__ = \ lambda x: '{}: ({})'.format(x.name, EnergySystemHandler.attr_to_dict(x)) def _new_resource_set(self): """Resets the resourceset (e.g. when loading a new file)""" self.rset = ResourceSet() self.resource = None self._set_resource_factories() def _set_resource_factories(self): # Assign files with the .esdl extension to the XMLResource instead of default XMI self.rset.resource_factory['esdl'] = XMLResource self.rset.resource_factory['*'] = XMLResource def load_file(self, uri_or_filename) -> (esdl.EnergySystem, []): """Loads a EnergySystem file or URI into a new resourceSet :returns EnergySystem and the parse warnings as a tuple (es, parse_info)""" if isinstance(uri_or_filename, str): if uri_or_filename[:4] == 'http': uri = HttpURI(uri_or_filename) else: uri = URI(uri_or_filename) else: uri = uri_or_filename return self.load_uri(uri) def import_file(self, uri_or_filename): """ :returns: EnergySystem and the parse warnings as a tuple (es, parse_info) """ if isinstance(uri_or_filename, str): if uri_or_filename[:4] == 'http': uri = HttpURI(uri_or_filename) else: uri = URI(uri_or_filename) else: uri = uri_or_filename return self.add_uri(uri) def load_uri(self, uri) -> (esdl.EnergySystem, []): """Loads a new resource in a new resourceSet :returns: EnergySystem and the parse warnings as a tuple (es, parse_info) """ self._new_resource_set() self.resource = self.rset.get_resource(uri) parse_info = [] if isinstance(self.resource, XMLResource): parse_info = self.resource.get_parse_information() # At this point, the model instance is loaded! self.energy_system = self.resource.contents[0] if isinstance(uri, str): self.esid_uri_dict[self.energy_system.id] = uri else: self.esid_uri_dict[self.energy_system.id] = uri.normalize() self.add_object_to_dict(self.energy_system.id, self.energy_system, False) return self.energy_system, parse_info def add_uri(self, uri): """ Adds the specified URI to the resource set, i.e. load extra resources that the resource can refer to. :returns: EnergySystem and the parse warnings as a tuple (es, parse_info) """ tmp_resource = self.rset.get_resource(uri) parse_info = [] if isinstance(tmp_resource, XMLResource): parse_info = tmp_resource.get_parse_information() # At this point, the model instance is loaded! # self.energy_system = self.resource.contents[0] self.validate(es=tmp_resource.contents[0]) if isinstance(uri, str): self.esid_uri_dict[tmp_resource.contents[0].id] = uri else: self.esid_uri_dict[tmp_resource.contents[0].id] = uri.normalize() # Edwin: recursive moet hier toch False zijn?? immers elke resource heeft zijn eigen uuid_dict # Ewoud: precies, dus in False veranderd self.add_object_to_dict(tmp_resource.contents[0].id, tmp_resource.contents[0], False) return tmp_resource.contents[0], parse_info def load_from_string(self, esdl_string, name='from_string'): """ Loads an energy system from a string and adds it to a *new* resourceSet :returns: EnergySystem and the parse warnings as a tuple (es, parse_info) """ if name is '': name = str(uuid4()) uri = StringURI(name + '.esdl', esdl_string) self._new_resource_set() self.resource = self.rset.create_resource(uri) try: self.resource.load() self.energy_system = self.resource.contents[0] parse_info = [] if isinstance(self.resource, XMLResource): parse_info = self.resource.get_parse_information() self.validate() self.esid_uri_dict[self.energy_system.id] = uri.normalize() # set to False, otherwise all the ids are added again after loading, is not smart and is slow # is only here to make sure the id of the energy system is also in the uuid_dict self.add_object_to_dict(self.energy_system.id, self.energy_system, False) return self.energy_system, parse_info except Exception as e: logger.error("Exception when loading resource: {}: {}".format( name, e)) raise def load_external_string(self, esdl_string, name='from_string'): """Loads an energy system from a string but does NOT add it to the resourceSet (e.g. as a separate resource) It returns an Energy System and parse info as a tuple, but the ES is not part of a resource in the ResourceSet """ uri = StringURI(name + '.esdl', esdl_string) external_rset = ResourceSet() external_resource = external_rset.create_resource(uri) external_resource.load() parse_info = [] if isinstance(external_resource, XMLResource): parse_info = external_resource.get_parse_information() external_energy_system = external_resource.contents[0] self.validate(es=external_energy_system) return external_energy_system, parse_info def add_from_string(self, name, esdl_string): """Loads an energy system from a string and adds it to the *existing* resourceSet :returns: EnergySystem and the parse warnings as a tuple (es, parse_info) """ uri = StringURI(name + '.esdl', esdl_string) # self.add_uri(uri) try: tmp_resource = self.rset.get_resource(uri) parse_info = [] if isinstance(tmp_resource, XMLResource): parse_info = tmp_resource.get_parse_information() tmp_es = tmp_resource.contents[0] self.validate(es=tmp_es) self.esid_uri_dict[tmp_es.id] = uri.normalize() self.add_object_to_dict(tmp_es.id, tmp_es, True) return tmp_resource.contents[0], parse_info except Exception as e: logger.error("Exception when loading resource: {}: {}".format( name, e)) raise def to_string(self, es_id=None): # to use strings as resources, we simulate a string as being a URI uri = StringURI('to_string_' + str(uuid4()) + '.esdl') if es_id is None: self.resource.save(uri) else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] resource = self.rset.resources[my_uri] resource.save(uri) else: # TODO: what to do? original behaviour self.resource.save(uri) # return the string return uri.getvalue() def to_bytesio(self): """Returns a BytesIO stream for the energy system""" uri = StringURI('bytes_io_to_string.esdl') self.resource.save(uri) return uri.get_stream() def save(self, es_id=None, filename=None): """Add the resource to the resourceSet when saving""" if filename is None: if es_id is None: self.resource.save() else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] resource = self.rset.resources[my_uri] resource.save() else: # TODO: what to do? original behaviour self.resource.save() else: uri = URI(filename) fileresource = self.rset.create_resource(uri) if es_id is None: # add the current energy system fileresource.append(self.energy_system) else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] es = self.rset.resources[my_uri].contents[0] fileresource.append(es) else: # TODO: what to do? original behaviour # add the current energy system fileresource.append(self.energy_system) # save the resource fileresource.save() self.rset.remove_resource(fileresource) def save_as(self, filename): """Saves the resource under a different filename""" self.resource.save(output=filename) def save_resourceSet(self): """Saves the complete resourceSet, including additional loaded resources encountered during loading of the initial resource""" for uri, resource in self.rset.resources.items(): logger.info('Saving {}'.format(uri)) resource.save( ) # raises an Error for HTTP URI, but not for CDOHttpURI def get_resource(self, es_id=None): if es_id is None: return self.resource else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] res = self.rset.resources[my_uri] return res else: return None def get_energy_system(self, es_id=None): if es_id is None: return self.energy_system else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] es = self.rset.resources[my_uri].contents[0] return es else: return None def remove_energy_system(self, es_id=None): if es_id is None: return else: my_uri = self.esid_uri_dict[es_id] del self.rset.resources[my_uri] del self.esid_uri_dict[es_id] def get_energy_systems(self): es_list = [] # for esid in self.esid_uri_dict: # uri = self.esid_uri_dict[esid] # es_list.append(self.rset.resources[uri]) for key in self.rset.resources.keys(): es_list.append(self.rset.resources[key].contents[0]) return es_list def validate(self, es=None): if es is None and self.energy_system is not None: es = self.energy_system if es is not None: if es.id is None: es.id = str(uuid4()) logger.warning( "Energysystem has no id, generating one: {}".format(es)) else: logger.warning("Can't validate EnergySystem {}".format(es)) # Using this function you can query for objects by ID # After loading an ESDL-file, all objects that have an ID defines are stored in resource.uuid_dict automatically # Note: If you add things later to the resource, it won't be added automatically to this dictionary though. # Use get_by_id_slow() for that or add them manually using add_object_to_dict() def get_by_id(self, es_id, object_id): if object_id in self.get_resource(es_id).uuid_dict: return self.get_resource(es_id).uuid_dict[object_id] else: logger.error( 'Can\'t find asset for id={} in uuid_dict of the ESDL model'. format(object_id)) #print(self.get_resource(es_id).uuid_dict) raise KeyError( 'Can\'t find asset for id={} in uuid_dict of the ESDL model'. format(object_id)) return None def add_object_to_dict(self, es_id: str, esdl_object: EObject, recursive=False): if recursive: for obj in esdl_object.eAllContents(): self.add_object_to_dict(es_id, obj) if hasattr(esdl_object, 'id'): if esdl_object.id is not None: self.get_resource(es_id).uuid_dict[ esdl_object.id] = esdl_object else: logger.warning('Id has not been set for object {}({})'.format( esdl_object.eClass.name, esdl_object)) def remove_object_from_dict(self, es_id, esdl_object: EObject, recursive=False): if recursive: for obj in esdl_object.eAllContents(): self.remove_object_from_dict(es_id, obj) if hasattr(esdl_object, 'id'): if esdl_object.id is not None and self.get_resource(es_id): del self.get_resource(es_id).uuid_dict[esdl_object.id] def remove_object_from_dict_by_id(self, es_id, object_id): del self.get_resource(es_id).uuid_dict[object_id] # returns a generator of all assets of a specific type. Not only the ones defined in the main Instance's Area # e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object # this function returns all of them at once # @staticmethod def get_all_instances_of_type(self, esdl_type, es_id): all_instances = list() for esdl_element in self.get_energy_system(es_id=es_id).eAllContents(): if isinstance(esdl_element, esdl_type): all_instances.append(esdl_element) return all_instances #return esdl_type.allInstances() # Creates a dict of all the attributes of an ESDL object, useful for printing/debugging @staticmethod def attr_to_dict(esdl_object): d = dict() d['esdlType'] = esdl_object.eClass.name for attr in dir(esdl_object): attr_value = esdl_object.eGet(attr) if attr_value is not None: d[attr] = attr_value return d # Creates a uuid: useful for generating unique IDs @staticmethod def generate_uuid(): return str(uuid4()) def create_empty_energy_system(self, name, es_description, inst_title, area_title, esdlVersion=None): es_id = str(uuid4()) self.energy_system = esdl.EnergySystem(id=es_id, name=name, description=es_description, esdlVersion=esdlVersion) uri = StringURI('empty_energysystem.esdl') self.resource = self.rset.create_resource(uri) # add the current energy system self.resource.append(self.energy_system) self.esid_uri_dict[self.energy_system.id] = uri.normalize() instance = esdl.Instance(id=str(uuid4()), name=inst_title) self.energy_system.instance.append(instance) # TODO: check if this (adding scope) solves error???? area = esdl.Area(id=str(uuid4()), name=area_title, scope=esdl.AreaScopeEnum.from_string('UNDEFINED')) instance.area = area # add generated id's to uuid dict self.add_object_to_dict(es_id, self.energy_system) self.add_object_to_dict(es_id, instance) self.add_object_to_dict(es_id, area) return self.energy_system # Support for Pickling when serializing the energy system in a session # The pyEcore classes by default do not allow for simple serialization for Session management in Flask. # Internally Flask Sessions use Pickle to serialize a data structure by means of its __dict__. This does not work. # Furthermore, ESDL can contain cyclic relations. Therefore we serialize to XMI and back if necessary. def __getstate__(self): state = dict() #print('Serialize rset {}'.format(self.rset.resources)) print('Serializing EnergySystem...', end="") state['energySystem'] = self.to_string() print('done') return state def __setstate__(self, state): self.__init__() #print('Deserialize rset {}'.format(self.rset.resources)) print('Deserializing EnergySystem...', end="") self.load_from_string(state['energySystem']) print('done') def update_version(self, es_id) -> str: """ Increments the version of this Energy System and returns it """ es = self.get_energy_system(es_id) version = '' if es.version is None else str(es.version) try: import re splitted = re.split(r"\D", version) print(splitted) major = splitted[0] major = int(major) + 1 except ValueError: major = 1 es.version = str(major) return es.version
class EnergySystemHandler: def __init__(self, energy_system=None): if energy_system is not None: self.energy_system = energy_system self.resource = None self.rset = ResourceSet() self.esid_uri_dict = {} self._set_resource_factories() # fix python builtin 'from' that is also used in ProfileElement as attribute # use 'start' instead of 'from' when using a ProfileElement # alias('start', esdl.ProfileElement.findEStructuralFeature('from')) esdl.ProfileElement.from_.name = 'from' setattr(esdl.ProfileElement, 'from', esdl.ProfileElement.from_) alias('start', esdl.ProfileElement.from_) esdl.FromToIntItem.from_.name = 'from' setattr(esdl.FromToIntItem, 'from', esdl.FromToIntItem.from_) alias('start', esdl.FromToIntItem.from_) esdl.FromToDoubleItem.from_.name = 'from' setattr(esdl.FromToDoubleItem, 'from', esdl.FromToDoubleItem.from_) alias('start', esdl.FromToDoubleItem.from_) # add support for shallow copying or cloning an object # it copies the object's attributes (e.g. clone an object), does only shallow copying def clone(self): """ Shallow copying or cloning an object It only copies the object's attributes (e.g. clone an object) Usage object.clone() or copy.copy(object) (as _copy__() is also implemented) :param self: :return: A clone of the object """ newone = type(self)() eclass = self.eClass for x in eclass.eAllStructuralFeatures(): if isinstance(x, EAttribute): #logger.trace("clone: processing attribute {}".format(x.name)) if x.many: eOrderedSet = newone.eGet(x.name) for v in self.eGet(x.name): eOrderedSet.append(v) else: newone.eSet(x.name, self.eGet(x.name)) return newone setattr(EObject, '__copy__', clone) setattr(EObject, 'clone', clone) """ Deep copying an EObject. Does not work yet for copying references from other resources than this one. """ def deepcopy(self, memo=None): logger.debug("deepcopy: processing {}".format(self)) first_call = False if memo is None: memo = dict() first_call = True if self in memo: return memo[self] copy: EObject = self.clone() logger.debug("Shallow copy: {}".format(copy)) eclass: EClass = self.eClass for x in eclass.eAllStructuralFeatures(): if isinstance(x, EReference): #logger.debug("deepcopy: processing reference {}".format(x.name)) ref: EReference = x value: EStructuralFeature = self.eGet(ref) if value is None: continue if ref.containment: if ref.many and isinstance(value, EAbstractSet): #clone all containment elements eAbstractSet = copy.eGet(ref.name) for ref_value in value: duplicate = ref_value.__deepcopy__(memo) eAbstractSet.append(duplicate) else: copy.eSet(ref.name, value.__deepcopy__(memo)) #else: # # no containment relation, but a reference # # this can only be done after a full copy # pass # now copy should a full copy, but without cross references memo[self] = copy if first_call: logger.debug("copying references") for k, v in memo.items(): eclass: EClass = k.eClass for x in eclass.eAllStructuralFeatures(): if isinstance(x, EReference): #logger.debug("deepcopy: processing x-reference {}".format(x.name)) ref: EReference = x orig_value: EStructuralFeature = k.eGet(ref) if orig_value is None: continue if not ref.containment: opposite = ref.eOpposite if opposite and opposite.containment: # do not handle eOpposite relations, they are handled automatically in pyEcore continue if x.many: eAbstractSet = v.eGet(ref.name) for orig_ref_value in orig_value: try: copy_ref_value = memo[ orig_ref_value] except KeyError: logger.warning( f'Cannot find reference of type {orig_ref_value.eClass.Name} \ for reference {k.eClass.name}.{ref.name} in deepcopy memo, using original' ) copy_ref_value = orig_ref_value eAbstractSet.append(copy_ref_value) else: try: copy_value = memo[orig_value] except KeyError: logger.warning( f'Cannot find reference of type {orig_value.eClass.name} of \ reference {k.eClass.name}.{ref.name} in deepcopy memo, using original' ) copy_value = orig_value v.eSet(ref.name, copy_value) return copy setattr(EObject, '__deepcopy__', deepcopy) setattr(EObject, 'deepcopy', deepcopy) # show deleted object from memory # setattr(EObject, '__del__', lambda x: print('Deleted {}'.format(x.eClass.name))) # def update_id(n: Notification): # if isinstance(n.feature, EAttribute): # #print(n) # if n.feature.name == 'id': # resource = n.notifier.eResource # if resource is not None and (n.kind != Kind.UNSET and n.kind != Kind.REMOVE): # print('ADDING to UUID dict {}#{}, notification type {}'.format(n.notifier.eClass.name, n.feature.name, n.kind.name)) # resource.uuid_dict[n.new] = n.notifier # if n.old is not None and n.old is not '': # del resource.uuid_dict[n.old] # observer = EObserver() # observer.notifyChanged = update_id # # old_init = EObject.__init__ # def new_init(self, **kwargs): # observer.observe(self) # old_init(self, **kwargs) # # setattr(EObject, '__init__', new_init) # Methods to automatically update the uuid_dict. # Currently disabled, because it does not work in all circumstances # This only works when the object which id is to be added to the dict is already part # of the energysystem xml tree, otherwise there is no way of knowing to which uuid_dict it should be added. # E.g. # > asset = esdl.Asset(id='uuid) # > asset.port.append(esdl.InPort(id='uuid)) # this does not work because asset is not part of the energy system yet # > area.asset.append(asset) #works for asset, but not for port. In order to have port working too, this statement # should be executed bofore adding the port... # old_set = EObject.__setattr__ # def updated_set(self, feature, value): # old_set(self, feature, value) # #if feature == 'id': # #print('Feature :{}#{}, value={}, resource={}'.format(self.eClass.name, feature, value, '?')) # #if isinstance(feature, EReference): # if hasattr(value, 'id') and feature[0] != '_': # print('*****Update uuid_dict {}#{} for {}#id'.format(self.eClass.name, feature, value.eClass.name)) # self.eResource.uuid_dict[value.id] = value # setattr(EObject, '__setattr__', updated_set) # # # # old_append = EAbstractSet.append # def updated_append(self, value, update_opposite=True): # old_append(self, value, update_opposite) # #print('EAbstractSet :{}, value={}, resource={}, featureEr={}'.format(self, value, value.eResource, self.feature.eResource)) # if hasattr(value, 'id'): # if self.feature.eResource: # print('****Update uuid_dict AbstractSet-{}#id'.format(value.eClass.name)) # self.feature.eResource.uuid_dict[value.id] = value # elif value.eResource: # print('****Update uuid_dict AbstractSet-{}#id'.format(value.eClass.name)) # value.eResource.uuid_dict[value.id] = value # # setattr(EAbstractSet, 'append', updated_append) # def toJSON(self): # return json.dumps(self, default=lambda o: list(o), # sort_keys=True, indent=4) # setattr(EOrderedSet, 'toJSON', toJSON) # have a nice __repr__ for some ESDL classes when printing ESDL objects (includes all Assets and EnergyAssets) esdl.EnergySystem.__repr__ = \ lambda x: '{}: ({})'.format(x.name, EnergySystemHandler.attr_to_dict(x)) def _new_resource_set(self): """Resets the resourceset (e.g. when loading a new file)""" self.rset = ResourceSet() self.resource = None self._set_resource_factories() def _set_resource_factories(self): # Assign files with the .esdl extension to the XMLResource instead of default XMI self.rset.resource_factory['esdl'] = XMLResource self.rset.resource_factory['*'] = XMLResource def load_file(self, uri_or_filename): """Loads a EnergySystem file or URI into a new resourceSet :returns EnergySystem the first item in the resourceSet""" if isinstance(uri_or_filename, str): if uri_or_filename[:4] == 'http': uri = HttpURI(uri_or_filename) else: uri = URI(uri_or_filename) else: uri = uri_or_filename return self.load_uri(uri) def import_file(self, uri_or_filename): if isinstance(uri_or_filename, str): if uri_or_filename[:4] == 'http': uri = HttpURI(uri_or_filename) else: uri = URI(uri_or_filename) else: uri = uri_or_filename return self.add_uri(uri) def load_uri(self, uri): """Loads a new resource in a new resourceSet""" self._new_resource_set() self.resource = self.rset.get_resource(uri) # At this point, the model instance is loaded! self.energy_system = self.resource.contents[0] if isinstance(uri, str): self.esid_uri_dict[self.energy_system.id] = uri else: self.esid_uri_dict[self.energy_system.id] = uri.normalize() self.add_object_to_dict(self.energy_system.id, self.energy_system, True) return self.energy_system def add_uri(self, uri): """Adds the specified URI to the resource set, i.e. load extra resources that the resource can refer to.""" tmp_resource = self.rset.get_resource(uri) # At this point, the model instance is loaded! # self.energy_system = self.resource.contents[0] self.validate(es=tmp_resource.contents[0]) if isinstance(uri, str): self.esid_uri_dict[tmp_resource.contents[0].id] = uri else: self.esid_uri_dict[tmp_resource.contents[0].id] = uri.normalize() # Edwin: recursive moet hier toch False zijn?? immers elke resource heeft zijn eigen uuid_dict self.add_object_to_dict(tmp_resource.contents[0].id, tmp_resource.contents[0], True) return tmp_resource.contents[0] def load_from_string(self, esdl_string, name='from_string'): """Loads an energy system from a string and adds it to a *new* resourceSet :returns the loaded EnergySystem """ uri = StringURI(name + '.esdl', esdl_string) self._new_resource_set() self.resource = self.rset.create_resource(uri) try: self.resource.load() self.energy_system = self.resource.contents[0] self.validate() self.esid_uri_dict[self.energy_system.id] = uri.normalize() # Edwin: recursive moet hier toch False zijn?? immers elke resource heeft zijn eigen uuid_dict self.add_object_to_dict(self.energy_system.id, self.energy_system, True) return self.energy_system except Exception as e: logger.error("Exception when loading resource: {}: {}".format( name, e)) raise def load_external_string(self, esdl_string, name='from_string'): """Loads an energy system from a string but does NOT add it to the resourceSet (e.g. as a separate resource) It returns an Energy System, but it is not part of a resource in the ResourceSet """ uri = StringURI(name + '.esdl', esdl_string) external_rset = ResourceSet() external_resource = external_rset.create_resource(uri) external_resource.load() external_energy_system = external_resource.contents[0] self.validate(es=external_energy_system) return external_energy_system def add_from_string(self, name, esdl_string): """Loads an energy system from a string and adds it to the *existing* resourceSet""" uri = StringURI(name + '.esdl', esdl_string) # self.add_uri(uri) tmp_resource = self.rset.get_resource(uri) tmp_es = tmp_resource.contents[0] try: self.validate(es=tmp_es) self.esid_uri_dict[tmp_es.id] = uri.normalize() self.add_object_to_dict(tmp_es.id, tmp_es, True) return tmp_resource.contents[0] except Exception as e: logger.error("Exception when loading resource: {}: {}".format( name, e)) raise def to_string(self, es_id=None): # to use strings as resources, we simulate a string as being a URI uri = StringURI('to_string_' + str(uuid4()) + '.esdl') if es_id is None: self.resource.save(uri) else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] resource = self.rset.resources[my_uri] resource.save(uri) else: # TODO: what to do? original behaviour self.resource.save(uri) # return the string return uri.getvalue() def to_bytesio(self): """Returns a BytesIO stream for the energy system""" uri = StringURI('bytes_io_to_string.esdl') self.resource.save(uri) return uri.get_stream() def save(self, es_id=None, filename=None): """Add the resource to the resourceSet when saving""" if filename is None: if es_id is None: self.resource.save() else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] resource = self.rset.resources[my_uri] resource.save() else: # TODO: what to do? original behaviour self.resource.save() else: uri = URI(filename) fileresource = self.rset.create_resource(uri) if es_id is None: # add the current energy system fileresource.append(self.energy_system) else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] es = self.rset.resources[my_uri].contents[0] fileresource.append(es) else: # TODO: what to do? original behaviour # add the current energy system fileresource.append(self.energy_system) # save the resource fileresource.save() self.rset.remove_resource(fileresource) def save_as(self, filename): """Saves the resource under a different filename""" self.resource.save(output=filename) def save_resourceSet(self): """Saves the complete resourceSet, including additional loaded resources encountered during loading of the initial resource""" for uri, resource in self.rset.resources.items(): logger.info('Saving {}'.format(uri)) resource.save( ) # raises an Error for HTTP URI, but not for CDOHttpURI def get_resource(self, es_id=None): if es_id is None: return self.resource else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] res = self.rset.resources[my_uri] return res else: return None def get_energy_system(self, es_id=None): if es_id is None: return self.energy_system else: if es_id in self.esid_uri_dict: my_uri = self.esid_uri_dict[es_id] es = self.rset.resources[my_uri].contents[0] return es else: return None def remove_energy_system(self, es_id=None): if es_id is None: return else: my_uri = self.esid_uri_dict[es_id] del self.rset.resources[my_uri] del self.esid_uri_dict[es_id] def get_energy_systems(self): es_list = [] # for esid in self.esid_uri_dict: # uri = self.esid_uri_dict[esid] # es_list.append(self.rset.resources[uri]) for key in self.rset.resources.keys(): es_list.append(self.rset.resources[key].contents[0]) return es_list def validate(self, es=None): if es is None and self.energy_system is not None: es = self.energy_system if es is not None: if es.id is None: es.id = str(uuid4()) logger.warning( "Energysystem has no id, generating one: {}".format(es)) else: logger.warning("Can't validate EnergySystem {}".format(es)) # Using this function you can query for objects by ID # After loading an ESDL-file, all objects that have an ID defines are stored in resource.uuid_dict automatically # Note: If you add things later to the resource, it won't be added automatically to this dictionary though. # Use get_by_id_slow() for that def get_by_id(self, es_id, object_id): if object_id in self.get_resource(es_id).uuid_dict: return self.get_resource(es_id).uuid_dict[object_id] else: print('Can\'t find asset for id={} in uuid_dict of the ESDL model'. format(object_id)) print(self.get_resource(es_id).uuid_dict) raise KeyError( 'Can\'t find asset for id={} in uuid_dict of the ESDL model'. format(object_id)) return None def add_object_to_dict(self, es_id: str, esdl_object: EObject, recursive=False): if recursive: for obj in esdl_object.eAllContents(): self.add_object_to_dict(es_id, obj) if hasattr(esdl_object, 'id'): if esdl_object.id is not None: self.get_resource(es_id).uuid_dict[ esdl_object.id] = esdl_object else: print('Id has not been set for object {}({})', esdl_object.eClass.name, esdl_object) def remove_object_from_dict(self, es_id, esdl_object): if hasattr(esdl_object, 'id'): if esdl_object.id is not None: del self.get_resource(es_id).uuid_dict[esdl_object.id] def remove_object_from_dict_by_id(self, es_id, object_id): del self.get_resource(es_id).uuid_dict[object_id] # returns a generator of all assets of a specific type. Not only the ones defined in the main Instance's Area # e.g. QuantityAndUnits can be defined in the KPI of an Area or in the EnergySystemInformation object # this function returns all of them at once # @staticmethod def get_all_instances_of_type(self, esdl_type, es_id): all_instances = list() for esdl_element in self.get_energy_system(es_id=es_id).eAllContents(): if isinstance(esdl_element, esdl_type): all_instances.append(esdl_element) return all_instances #return esdl_type.allInstances() # Creates a dict of all the attributes of an ESDL object, useful for printing/debugging @staticmethod def attr_to_dict(esdl_object): d = dict() d['esdlType'] = esdl_object.eClass.name for attr in dir(esdl_object): attr_value = esdl_object.eGet(attr) if attr_value is not None: d[attr] = attr_value return d # Creates a uuid: useful for generating unique IDs @staticmethod def generate_uuid(): return str(uuid4()) def create_empty_energy_system(self, name, es_description, inst_title, area_title): es_id = str(uuid4()) self.energy_system = esdl.EnergySystem(id=es_id, name=name, description=es_description) uri = StringURI('empty_energysystem.esdl') self.resource = self.rset.create_resource(uri) # add the current energy system self.resource.append(self.energy_system) self.esid_uri_dict[self.energy_system.id] = uri.normalize() instance = esdl.Instance(id=str(uuid4()), name=inst_title) self.energy_system.instance.append(instance) # TODO: check if this (adding scope) solves error???? area = esdl.Area(id=str(uuid4()), name=area_title, scope=esdl.AreaScopeEnum.from_string('UNDEFINED')) instance.area = area # add generated id's to uuid dict self.add_object_to_dict(es_id, self.energy_system) self.add_object_to_dict(es_id, instance) self.add_object_to_dict(es_id, area) return self.energy_system # Support for Pickling when serializing the energy system in a session # The pyEcore classes by default do not allow for simple serialization for Session management in Flask. # Internally Flask Sessions use Pickle to serialize a data structure by means of its __dict__. This does not work. # Furthermore, ESDL can contain cyclic relations. Therefore we serialize to XMI and back if necessary. def __getstate__(self): state = dict() #print('Serialize rset {}'.format(self.rset.resources)) print('Serializing EnergySystem...', end="") state['energySystem'] = self.to_string() print('done') return state def __setstate__(self, state): self.__init__() #print('Deserialize rset {}'.format(self.rset.resources)) print('Deserializing EnergySystem...', end="") self.load_from_string(state['energySystem']) print('done')