From 635c79da16c72f5ce186edd94a0c8a65850a2923 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 16 Aug 2023 12:02:46 +0200 Subject: [PATCH] Refactor Adapter to not init facade --- data_adapter_oemof/adapters.py | 147 ++++++++------------------------- data_adapter_oemof/mappings.py | 4 +- tests/test_data_adapter.py | 45 +++++++++- 3 files changed, 81 insertions(+), 115 deletions(-) diff --git a/data_adapter_oemof/adapters.py b/data_adapter_oemof/adapters.py index 15723cf..d8013b4 100644 --- a/data_adapter_oemof/adapters.py +++ b/data_adapter_oemof/adapters.py @@ -1,63 +1,28 @@ -import dataclasses import logging -import oemof.solph -import pandas - from oemof.tabular import facades -from oemof.solph import Bus +from oemof.tabular._facade import Facade from data_adapter_oemof import calculations from data_adapter_oemof.mappings import Mapper - logger = logging.getLogger() class Adapter: type: str = "adapter" + facade: Facade = None extra_attributes = ("name", "type") - def as_dict(self): - """ - Adds function to return DataFrame from adapter. - - This mixin is needed because `pd.DataFrame(dataclass_instance)` creates columns - only for existing dataclass attributes. - But we add custom_attributes (i.e. "name") which would be neglected. - """ - fields = dataclasses.fields(self) - data = {} - for field in fields: - value = getattr(self, field.name) - data[field.name] = value - if isinstance(value, oemof.solph._plumbing._Sequence): - if value.periodic_values: - data[field.name] = value.periodic_values - elif len(value) != 0: - data[field.name] = value.data - else: - data[field.name] = value.default - # data = {field.name: field_value - # for field in fields - # if isinstance((field_value := getattr(self, field.name)), oemof.solph._plumbing._Sequence) - # } - for attr in self.extra_attributes: - data[attr] = getattr(self, attr) - return data - - @classmethod - def parametrize_dataclass( - cls, - struct, - mapper: Mapper, - ) -> "Adapter": - return cls(**cls.get_default_parameters(struct, mapper)) - - @classmethod - def get_default_parameters(cls, struct: dict, mapper: Mapper) -> dict: + def as_dict(self) -> dict: + return self.facade_dict + + def __init__(self, struct: dict, mapper: Mapper): + self.facade_dict = self.get_default_parameters(struct, mapper) + + def get_default_parameters(self, struct: dict, mapper: Mapper) -> dict: defaults = { - "type": cls.type, + "type": self.type, } # Add mapped attributes mapped_values = mapper.get_default_mappings(struct) @@ -75,146 +40,104 @@ def get_default_parameters(cls, struct: dict, mapper: Mapper) -> dict: return defaults -def facade_adapter(cls): - r""" - Sets type of Busses to str - Sets 'build_solph_components' to False in __init__. - """ - # set type of bus to str - for field in dataclasses.fields(cls): - if field.type == Bus: - field.type = str - - original_init = cls.__init__ - - # do not build solph components - def new_init(self, *args, **kwargs): - custom_attributes = {} - original_fields = tuple(field.name for field in dataclasses.fields(cls)) - for key in tuple(kwargs.keys()): - if key not in original_fields: - custom_attributes[key] = kwargs.pop(key) - - original_init(self, *args, build_solph_components=False, **kwargs) - - for key, value in custom_attributes.items(): - setattr(self, key, value) - - cls.__init__ = new_init - - return cls - - -@facade_adapter -class DispatchableAdapter(facades.Dispatchable, Adapter): +class DispatchableAdapter(Adapter): """ Dispatchable Adapter """ - type = "dispatchable" + facade = facades.Dispatchable -@facade_adapter -class HeatPumpAdapter(facades.HeatPump, Adapter): +class HeatPumpAdapter(Adapter): """ HeatPump Adapter """ - type = "heat_pump" + facade = facades.HeatPump -@facade_adapter -class LinkAdapter(facades.Link, Adapter): +class LinkAdapter(Adapter): """ Link Adapter """ - type = "link" + facade = facades.Link -@facade_adapter -class ReservoirAdapter(facades.Reservoir, Adapter): +class ReservoirAdapter(Adapter): """ Reservoir Adapter """ - type = "reservoir" + facade = facades.Reservoir -@facade_adapter -class ExcessAdapter(facades.Excess, Adapter): +class ExcessAdapter(Adapter): """ Excess Adapter """ - type = "excess" + facade = facades.Excess -@facade_adapter -class BackpressureTurbineAdapter(facades.BackpressureTurbine, Adapter): +class BackpressureTurbineAdapter(Adapter): """ BackpressureTurbine Adapter """ - type = "backpressure_turbine" + facade = facades.BackpressureTurbine -@facade_adapter -class CommodityAdapter(facades.Commodity, Adapter): +class CommodityAdapter(Adapter): """ CommodityAdapter """ - type = "commodity" + facade = facades.Commodity -@facade_adapter -class ConversionAdapter(facades.Conversion, Adapter): +class ConversionAdapter(Adapter): """ ConversionAdapter To use Conversion, map the inputs and outputs within the structure to avoid deduction failure. """ - type = "conversion" + facade = facades.Conversion -@facade_adapter -class LoadAdapter(facades.Load, Adapter): +class LoadAdapter(Adapter): """ LoadAdapter """ - type = "load" + facade = facades.Load -@facade_adapter -class StorageAdapter(facades.Storage, Adapter): +class StorageAdapter(Adapter): """ StorageAdapter """ - type = "storage" + facade = facades.Storage -@facade_adapter -class ExtractionTurbineAdapter(facades.ExtractionTurbine, Adapter): +class ExtractionTurbineAdapter(Adapter): """ ExtractionTurbineAdapter """ - type = "extraction_turbine" + facade = facades.ExtractionTurbine -@facade_adapter -class VolatileAdapter(facades.Volatile, Adapter): +class VolatileAdapter(Adapter): """ VolatileAdapter """ - type = "volatile" + facade = facades.Volatile # Create a dictionary of all adapter classes defined in this module FACADE_ADAPTERS = { - name: adapter for name, adapter in globals().items() if name.endswith("Adapter") + adapter.type: adapter for name, adapter in globals().items() if name.endswith("Adapter") } diff --git a/data_adapter_oemof/mappings.py b/data_adapter_oemof/mappings.py index e90a535..088f2a5 100644 --- a/data_adapter_oemof/mappings.py +++ b/data_adapter_oemof/mappings.py @@ -143,7 +143,7 @@ def get_busses(self, struct): """ bus_occurrences_in_fields = [ field.name - for field in dataclasses.fields(self.adapter) + for field in dataclasses.fields(self.adapter.facade) if "bus" in field.name ] if len(bus_occurrences_in_fields) == 0: @@ -213,7 +213,7 @@ def get_default_mappings(self, struct): mapped_all_class_fields = { field.name: value - for field in dataclasses.fields(self.adapter) + for field in dataclasses.fields(self.adapter.facade) if (value := self.get(field.name, field.type)) is not None } mapped_all_class_fields.update(self.get_busses(struct)) diff --git a/tests/test_data_adapter.py b/tests/test_data_adapter.py index a0ea928..e1b23ef 100644 --- a/tests/test_data_adapter.py +++ b/tests/test_data_adapter.py @@ -3,12 +3,55 @@ import pandas as pd -from data_adapter_oemof.adapters import TYPE_MAP +from data_adapter_oemof.mappings import Mapper +from data_adapter_oemof.adapters import VolatileAdapter, FACADE_ADAPTERS TEST_DIR = pathlib.Path(__file__).parent TEMP_DIR = TEST_DIR / "_temp" +def test_simple_adapter(): + data = { + "type": "volatile", + "region": "B", + "carrier": "wind", + "bus": "electricity", + "tech": "onshore", + "capacity": 12, + "sedos-marginal_cost": 1, + "sedos-overnight_cost": 1000, + "capital_costs": 100, + "sedos-lifetime": 25, + "sedos-wacc": 0.05, + } + timeseries = pd.DataFrame({"a": [0, 0, 0], "onshore_B": [1, 2, 3]}) + struct = {"default": {"inputs": ["onshore"], "outputs": ["electricity"]}} + mapping = { + "modex_tech_wind_turbine_onshore": {"profile": "onshore", "lifetime": "sedos-lifetime"} + } + mapper = Mapper( + adapter=VolatileAdapter, + process_name="modex_tech_wind_turbine_onshore", + data=data, + timeseries=timeseries, + mapping=mapping, + ) + adapter = VolatileAdapter(struct, mapper) + expected_dict = { + "bus": "electricity", + "capacity": 12, + "carrier": "wind", + "name": "B-None-onshore", + "profile": "onshore_B", + "tech": "onshore", + "type": "volatile", + "lifetime": 25, + "region": "B", + "year": None + } + assert adapter.as_dict() == expected_dict + + def test_adapter(): Component = namedtuple("Component", ["type", "data"])