From 50d6faed6f62f69669e1ec74535ce0f2e635720e Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 27 Nov 2024 11:58:10 +0100 Subject: [PATCH 1/9] Remove pointless indexsets.tabulate parameter --- ixmp4/core/optimization/indexset.py | 6 ++-- ixmp4/data/abstract/optimization/indexset.py | 7 +--- ixmp4/data/api/optimization/indexset.py | 6 ++-- ixmp4/data/db/base.py | 2 -- .../db/optimization/indexset/repository.py | 17 ++-------- ixmp4/server/rest/optimization/indexset.py | 2 -- tests/core/test_optimization_indexset.py | 32 ++++++------------- tests/data/test_optimization_indexset.py | 32 ++++++------------- 8 files changed, 25 insertions(+), 79 deletions(-) diff --git a/ixmp4/core/optimization/indexset.py b/ixmp4/core/optimization/indexset.py index cd901150..36a3fc5f 100644 --- a/ixmp4/core/optimization/indexset.py +++ b/ixmp4/core/optimization/indexset.py @@ -113,9 +113,7 @@ def list(self, name: str | None = None) -> list[IndexSet]: for i in indexsets ] - def tabulate( - self, name: str | None = None, include_data: bool = False - ) -> pd.DataFrame: + def tabulate(self, name: str | None = None) -> pd.DataFrame: return self.backend.optimization.indexsets.tabulate( - run_id=self._run.id, name=name, include_data=include_data + run_id=self._run.id, name=name ) diff --git a/ixmp4/data/abstract/optimization/indexset.py b/ixmp4/data/abstract/optimization/indexset.py index 82feea2d..cfacc96b 100644 --- a/ixmp4/data/abstract/optimization/indexset.py +++ b/ixmp4/data/abstract/optimization/indexset.py @@ -105,16 +105,11 @@ def list(self, **kwargs: Unpack["EnumerateKwargs"]) -> list[IndexSet]: """ ... - def tabulate( - self, *, include_data: bool = False, **kwargs: Unpack["EnumerateKwargs"] - ) -> pd.DataFrame: + def tabulate(self, **kwargs: Unpack["EnumerateKwargs"]) -> pd.DataFrame: r"""Tabulate IndexSets by specified criteria. Parameters ---------- - include_data : bool, optional - Whether to load all IndexSet data, which reduces loading speed. Defaults to - `False`. \*\*kwargs: any Any filter parameters as specified in `ixmp4.data.db.optimization.indexset.filter.OptimizationIndexSetFilter`. diff --git a/ixmp4/data/api/optimization/indexset.py b/ixmp4/data/api/optimization/indexset.py index 7e3b4a8c..0feb91c9 100644 --- a/ixmp4/data/api/optimization/indexset.py +++ b/ixmp4/data/api/optimization/indexset.py @@ -66,12 +66,10 @@ def list( return super()._list(json=json) def tabulate( - self, - include_data: bool = False, - **kwargs: Unpack[abstract.optimization.EnumerateKwargs], + self, **kwargs: Unpack[abstract.optimization.EnumerateKwargs] ) -> pd.DataFrame: json = cast(abstract.annotations.OptimizationFilterAlias, kwargs) - return super()._tabulate(json=json, params={"include_data": include_data}) + return super()._tabulate(json=json) def add_data( self, diff --git a/ixmp4/data/db/base.py b/ixmp4/data/db/base.py index 56b68f8c..971737fe 100644 --- a/ixmp4/data/db/base.py +++ b/ixmp4/data/db/base.py @@ -290,7 +290,6 @@ def tabulate(self, *args: Any, _raw: bool = False, **kwargs: Any) -> pd.DataFram class PaginateKwargs(TypedDict): _filter: filters.BaseFilter - include_data: NotRequired[bool] join_parameters: NotRequired[bool | None] join_runs: NotRequired[bool | None] join_run_index: NotRequired[bool | None] @@ -299,7 +298,6 @@ class PaginateKwargs(TypedDict): class EnumerateKwargs(TypedDict): _filter: filters.BaseFilter - include_data: NotRequired[bool] join_parameters: NotRequired[bool | None] join_runs: NotRequired[bool | None] join_run_index: NotRequired[bool | None] diff --git a/ixmp4/data/db/optimization/indexset/repository.py b/ixmp4/data/db/optimization/indexset/repository.py index 32455dd1..c559ce23 100644 --- a/ixmp4/data/db/optimization/indexset/repository.py +++ b/ixmp4/data/db/optimization/indexset/repository.py @@ -66,21 +66,8 @@ def list(self, **kwargs: Unpack["base.EnumerateKwargs"]) -> list[IndexSet]: return super().list(**kwargs) @guard("view") - def tabulate( - self, *, include_data: bool = False, **kwargs: Unpack["base.EnumerateKwargs"] - ) -> pd.DataFrame: - if not include_data: - return ( - super().tabulate(**kwargs).rename(columns={"_data_type": "data_type"}) - ) - else: - result = super().tabulate(**kwargs).drop(labels="_data_type", axis=1) - result.insert( - loc=0, - column="data", - value=[indexset.data for indexset in self.list(**kwargs)], - ) - return result + def tabulate(self, **kwargs: Unpack["base.EnumerateKwargs"]) -> pd.DataFrame: + return super().tabulate(**kwargs).rename(columns={"_data_type": "data_type"}) @guard("edit") def add_data( diff --git a/ixmp4/server/rest/optimization/indexset.py b/ixmp4/server/rest/optimization/indexset.py index 97cda612..91d92956 100644 --- a/ixmp4/server/rest/optimization/indexset.py +++ b/ixmp4/server/rest/optimization/indexset.py @@ -29,7 +29,6 @@ class DataInput(BaseModel): def query( filter: OptimizationIndexSetFilter = Body(OptimizationIndexSetFilter()), table: bool = Query(False), - include_data: bool = Query(False), pagination: Pagination = Depends(), backend: Backend = Depends(deps.get_backend), ) -> EnumerationOutput[IndexSet]: @@ -39,7 +38,6 @@ def query( limit=pagination.limit, offset=pagination.offset, table=bool(table), - include_data=bool(include_data), ), total=backend.optimization.indexsets.count(_filter=filter), pagination=pagination, diff --git a/tests/core/test_optimization_indexset.py b/tests/core/test_optimization_indexset.py index a3a80a35..c6b949b5 100644 --- a/tests/core/test_optimization_indexset.py +++ b/tests/core/test_optimization_indexset.py @@ -9,7 +9,7 @@ from ..utils import create_indexsets_for_run -def df_from_list(indexsets: list[IndexSet], include_data: bool = False) -> pd.DataFrame: +def df_from_list(indexsets: list[IndexSet]) -> pd.DataFrame: result = pd.DataFrame( # Order is important here to avoid utils.assert_unordered_equality, # which doesn't like lists @@ -31,19 +31,14 @@ def df_from_list(indexsets: list[IndexSet], include_data: bool = False) -> pd.Da "created_by", ], ) - if include_data: - result.insert( - loc=0, column="data", value=[indexset.data for indexset in indexsets] - ) - else: - result.insert( - loc=0, - column="data_type", - value=[ - type(indexset.data[0]).__name__ if indexset.data != [] else None - for indexset in indexsets - ], - ) + result.insert( + loc=0, + column="data_type", + value=[ + type(indexset.data[0]).__name__ if indexset.data != [] else None + for indexset in indexsets + ], + ) return result @@ -143,15 +138,6 @@ def test_tabulate_indexsets(self, platform: ixmp4.Platform) -> None: result = run.optimization.indexsets.tabulate(name="Indexset 2") pdt.assert_frame_equal(expected, result) - # Test tabulating including the data - expected = df_from_list(indexsets=[indexset_2], include_data=True) - pdt.assert_frame_equal( - expected, - run.optimization.indexsets.tabulate( - name=indexset_2.name, include_data=True - ), - ) - def test_indexset_docs(self, platform: ixmp4.Platform) -> None: run = platform.runs.create("Model", "Scenario") (indexset_1,) = tuple( diff --git a/tests/data/test_optimization_indexset.py b/tests/data/test_optimization_indexset.py index 89e6f4d1..20e39311 100644 --- a/tests/data/test_optimization_indexset.py +++ b/tests/data/test_optimization_indexset.py @@ -9,7 +9,7 @@ from ..utils import create_indexsets_for_run -def df_from_list(indexsets: list[IndexSet], include_data: bool = False) -> pd.DataFrame: +def df_from_list(indexsets: list[IndexSet]) -> pd.DataFrame: result = pd.DataFrame( # Order is important here to avoid utils.assert_unordered_equality, # which doesn't like lists @@ -31,19 +31,14 @@ def df_from_list(indexsets: list[IndexSet], include_data: bool = False) -> pd.Da "created_by", ], ) - if include_data: - result.insert( - loc=0, column="data", value=[indexset.data for indexset in indexsets] - ) - else: - result.insert( - loc=0, - column="data_type", - value=[ - type(indexset.data[0]).__name__ if indexset.data != [] else None - for indexset in indexsets - ], - ) + result.insert( + loc=0, + column="data_type", + value=[ + type(indexset.data[0]).__name__ if indexset.data != [] else None + for indexset in indexsets + ], + ) return result @@ -138,15 +133,6 @@ def test_tabulate_indexsets(self, platform: ixmp4.Platform) -> None: expected, platform.backend.optimization.indexsets.tabulate(run_id=run_2.id) ) - # Test tabulating including the data - expected = df_from_list(indexsets=[indexset_2], include_data=True) - pdt.assert_frame_equal( - expected, - platform.backend.optimization.indexsets.tabulate( - name=indexset_2.name, include_data=True - ), - ) - def test_add_data(self, platform: ixmp4.Platform) -> None: test_data = ["foo", "bar"] run = platform.backend.runs.create("Model", "Scenario") From 75004d277b06d4b099fc0d935ea53c803381da9d Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Mon, 1 Jul 2024 15:51:15 +0200 Subject: [PATCH 2/9] Add abstraction layer for core optimization reusability --- ixmp4/core/optimization/base.py | 93 ++++++++++++++++++++ ixmp4/core/optimization/table.py | 54 +++--------- ixmp4/data/abstract/__init__.py | 1 + ixmp4/data/abstract/optimization/__init__.py | 1 + ixmp4/data/abstract/optimization/base.py | 53 +++++++++++ 5 files changed, 159 insertions(+), 43 deletions(-) create mode 100644 ixmp4/core/optimization/base.py create mode 100644 ixmp4/data/abstract/optimization/base.py diff --git a/ixmp4/core/optimization/base.py b/ixmp4/core/optimization/base.py new file mode 100644 index 00000000..d1be682a --- /dev/null +++ b/ixmp4/core/optimization/base.py @@ -0,0 +1,93 @@ +# from typing import Any, ClassVar, Generic, TypeVar +from typing import Generic, TypeVar + +import pandas as pd + +from ixmp4.core.base import BaseFacade, BaseModelFacade + +# from ixmp4.core.exceptions import IxmpError +from ixmp4.data import abstract + +# class OptimizationBaseModel(BaseModelFacade): +# _model: abstract.OptimizationBaseModel +# NotFound: ClassVar[type[IxmpError]] +# NotUnique: ClassVar[type[IxmpError]] + +# @property +# def id(self) -> int: +# return self._model.id + +# @property +# def name(self) -> str: +# return self._model.name + +# @property +# def run_id(self) -> int: +# return self._model.run__id + +# @property +# def data(self) -> dict[str, Any]: +# return self._model.data + +# # TODO Stopping here since I'm not convinced of the usefulness. The only benefit I +# # see is that we'd separate functional code from placeholders so that if we need +# # to change functionality for e.g. getting 'data', we only need to do so in one +# # place. Still this feels like a lot of repetition. + + +OptimizationModelType = TypeVar("OptimizationModelType", bound=BaseModelFacade) + + +class OptimizationBaseRepository(BaseFacade, Generic[OptimizationModelType]): + _run: abstract.Run + _backend_repository: abstract.BackendBaseRepository + _model_type: type[OptimizationModelType] + + def __init__(self, _run: abstract.Run, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._run = _run + + +class Creator(OptimizationBaseRepository[OptimizationModelType], abstract.Creator): + def create( + self, + name: str, + constrained_to_indexsets: list[str], + column_names: list[str] | None = None, + ) -> OptimizationModelType: + model = self._backend_repository.create( + name=name, + run_id=self._run.id, + constrained_to_indexsets=constrained_to_indexsets, + column_names=column_names, + ) + return self._model_type(_backend=self.backend, _model=model) + + +class Retriever(OptimizationBaseRepository[OptimizationModelType], abstract.Retriever): + def get(self, name: str, *args, **kwargs) -> OptimizationModelType: + model = self._backend_repository.get(run_id=self._run.id, name=name) + return self._model_type(_backend=self.backend, _model=model) + + +class Lister(OptimizationBaseRepository[OptimizationModelType], abstract.Lister): + def list(self, name: str | None = None) -> list[OptimizationModelType]: + models = self._backend_repository.list(run_id=self._run.id, name=name) + return [self._model_type(_backend=self.backend, _model=m) for m in models] + + +class Tabulator(OptimizationBaseRepository[OptimizationModelType], abstract.Tabulator): + def tabulate(self, name: str | None = None) -> pd.DataFrame: + return self._backend_repository.tabulate(name=name) + + +class Enumerator( + Lister[OptimizationModelType], Tabulator[OptimizationModelType], abstract.Enumerator +): + def enumerate( + self, *args, table: bool = False, **kwargs + ) -> list[OptimizationModelType] | pd.DataFrame: + if table: + return self.tabulate(*args, **kwargs) + else: + return self.list(*args, **kwargs) diff --git a/ixmp4/core/optimization/table.py b/ixmp4/core/optimization/table.py index 3374c209..3afb5dc1 100644 --- a/ixmp4/core/optimization/table.py +++ b/ixmp4/core/optimization/table.py @@ -1,21 +1,19 @@ -from collections.abc import Iterable from datetime import datetime from typing import TYPE_CHECKING, Any, ClassVar if TYPE_CHECKING: - from . import InitKwargs + pass import pandas as pd # TODO Import this from typing when dropping Python 3.11 -from typing_extensions import Unpack - -from ixmp4.core.base import BaseFacade, BaseModelFacade +from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel -from ixmp4.data.abstract import Run from ixmp4.data.abstract import Table as TableModel from ixmp4.data.abstract.optimization import Column +from .base import Creator, Lister, Retriever, Tabulator + class Table(BaseModelFacade): _model: TableModel @@ -87,40 +85,10 @@ def __str__(self) -> str: return f"" -class TableRepository(BaseFacade): - _run: Run - - def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: - super().__init__(**kwargs) - self._run = _run - - def create( - self, - name: str, - constrained_to_indexsets: list[str], - column_names: list[str] | None = None, - ) -> Table: - model = self.backend.optimization.tables.create( - name=name, - run_id=self._run.id, - constrained_to_indexsets=constrained_to_indexsets, - column_names=column_names, - ) - return Table(_backend=self.backend, _model=model) - - def get(self, name: str) -> Table: - model = self.backend.optimization.tables.get(run_id=self._run.id, name=name) - return Table(_backend=self.backend, _model=model) - - def list(self, name: str | None = None) -> Iterable[Table]: - tables = self.backend.optimization.tables.list(run_id=self._run.id, name=name) - return [ - Table( - _backend=self.backend, - _model=i, - ) - for i in tables - ] - - def tabulate(self, name: str | None = None) -> pd.DataFrame: - return self.backend.optimization.tables.tabulate(run_id=self._run.id, name=name) +class TableRepository( + Creator[Table], Retriever[Table], Lister[Table], Tabulator[Table] +): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._backend_repository = self.backend.optimization.tables + self._model_type = Table diff --git a/ixmp4/data/abstract/__init__.py b/ixmp4/data/abstract/__init__.py index adf7ef7d..787bccee 100644 --- a/ixmp4/data/abstract/__init__.py +++ b/ixmp4/data/abstract/__init__.py @@ -30,6 +30,7 @@ from .meta import MetaValue, RunMetaEntry, RunMetaEntryRepository, StrictMetaValue from .model import Model, ModelRepository from .optimization import ( + BackendBaseRepository, Equation, EquationRepository, IndexSet, diff --git a/ixmp4/data/abstract/optimization/__init__.py b/ixmp4/data/abstract/optimization/__init__.py index 8d95e132..97ace0ef 100644 --- a/ixmp4/data/abstract/optimization/__init__.py +++ b/ixmp4/data/abstract/optimization/__init__.py @@ -1,6 +1,7 @@ from collections.abc import Iterable from ..annotations import HasIdFilter, HasNameFilter, HasRunIdFilter +from .base import BackendBaseRepository from .column import Column from .equation import Equation, EquationRepository from .indexset import IndexSet, IndexSetRepository diff --git a/ixmp4/data/abstract/optimization/base.py b/ixmp4/data/abstract/optimization/base.py new file mode 100644 index 00000000..b77fec7f --- /dev/null +++ b/ixmp4/data/abstract/optimization/base.py @@ -0,0 +1,53 @@ +from typing import Any, Generic, Iterable, Protocol, TypeVar + +import pandas as pd + +# from ixmp4.data import types +from .. import base +from ..docs import DocsRepository + +# from .column import Column + + +# TODO Currently not in use +# class OptimizationBaseModel(base.BaseModel, Protocol): +# id: types.Integer +# name: types.String +# data: types.JsonDict +# columns: types.Mapped[list[Column]] +# run__id: types.Integer +# created_at: types.DateTime +# created_by: types.String + + +BackendModelType = TypeVar("BackendModelType", bound=base.BaseModel, covariant=True) + + +class BackendBaseRepository( + Generic[BackendModelType], + base.Creator, + base.Retriever, + base.Enumerator, + Protocol, +): + docs: DocsRepository + + def create( + self, + run_id: int, + name: str, + constrained_to_indexsets: list[str], + column_names: list[str] | None = None, + ) -> BackendModelType: ... + + def get(self, run_id: int, name: str) -> BackendModelType: ... + + def get_by_id(self, id: int) -> BackendModelType: ... + + def list( + self, *, name: str | None = None, **kwargs + ) -> Iterable[BackendModelType]: ... + + def tabulate(self, *, name: str | None = None, **kwargs) -> pd.DataFrame: ... + + def add_data(self, table_id: int, data: dict[str, Any] | pd.DataFrame) -> None: ... From 22d4dc1bf9c4b40abfb99f0ba75a5b2bcce3835a Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 2 Jul 2024 08:54:32 +0200 Subject: [PATCH 3/9] Use abstraction layer for core optimization indexset --- ixmp4/core/optimization/base.py | 13 +++--- ixmp4/core/optimization/indexset.py | 52 +++++------------------- ixmp4/data/abstract/optimization/base.py | 17 +++++--- 3 files changed, 29 insertions(+), 53 deletions(-) diff --git a/ixmp4/core/optimization/base.py b/ixmp4/core/optimization/base.py index d1be682a..6f281905 100644 --- a/ixmp4/core/optimization/base.py +++ b/ixmp4/core/optimization/base.py @@ -52,14 +52,15 @@ class Creator(OptimizationBaseRepository[OptimizationModelType], abstract.Creato def create( self, name: str, - constrained_to_indexsets: list[str], - column_names: list[str] | None = None, + # TODO But how do we know show in core layer that e.g. Table needs these? + # constrained_to_indexsets: list[str], + # column_names: list[str] | None = None, + *args, + **kwargs, ) -> OptimizationModelType: model = self._backend_repository.create( - name=name, - run_id=self._run.id, - constrained_to_indexsets=constrained_to_indexsets, - column_names=column_names, + *args, + **dict(kwargs, name=name, run_id=self._run.id), ) return self._model_type(_backend=self.backend, _model=model) diff --git a/ixmp4/core/optimization/indexset.py b/ixmp4/core/optimization/indexset.py index 36a3fc5f..e6e59092 100644 --- a/ixmp4/core/optimization/indexset.py +++ b/ixmp4/core/optimization/indexset.py @@ -2,17 +2,16 @@ from typing import TYPE_CHECKING, ClassVar if TYPE_CHECKING: - from . import InitKwargs + pass -import pandas as pd # TODO Import this from typing when dropping Python 3.11 -from typing_extensions import Unpack -from ixmp4.core.base import BaseFacade, BaseModelFacade +from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel from ixmp4.data.abstract import IndexSet as IndexSetModel -from ixmp4.data.abstract import Run + +from .base import Creator, Lister, Retriever, Tabulator class IndexSet(BaseModelFacade): @@ -81,39 +80,10 @@ def __str__(self) -> str: return f"" -class IndexSetRepository(BaseFacade): - _run: Run - - def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: - super().__init__(**kwargs) - self._run = _run - - def create(self, name: str) -> IndexSet: - indexset = self.backend.optimization.indexsets.create( - run_id=self._run.id, - name=name, - ) - return IndexSet(_backend=self.backend, _model=indexset) - - def get(self, name: str) -> IndexSet: - indexset = self.backend.optimization.indexsets.get( - run_id=self._run.id, name=name - ) - return IndexSet(_backend=self.backend, _model=indexset) - - def list(self, name: str | None = None) -> list[IndexSet]: - indexsets = self.backend.optimization.indexsets.list( - run_id=self._run.id, name=name - ) - return [ - IndexSet( - _backend=self.backend, - _model=i, - ) - for i in indexsets - ] - - def tabulate(self, name: str | None = None) -> pd.DataFrame: - return self.backend.optimization.indexsets.tabulate( - run_id=self._run.id, name=name - ) +class IndexSetRepository( + Creator[IndexSet], Retriever[IndexSet], Lister[IndexSet], Tabulator[IndexSet] +): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._backend_repository = self.backend.optimization.indexsets + self._model_type = IndexSet diff --git a/ixmp4/data/abstract/optimization/base.py b/ixmp4/data/abstract/optimization/base.py index b77fec7f..b10762db 100644 --- a/ixmp4/data/abstract/optimization/base.py +++ b/ixmp4/data/abstract/optimization/base.py @@ -1,4 +1,4 @@ -from typing import Any, Generic, Iterable, Protocol, TypeVar +from typing import Generic, Iterable, Protocol, TypeVar import pandas as pd @@ -36,18 +36,23 @@ def create( self, run_id: int, name: str, - constrained_to_indexsets: list[str], - column_names: list[str] | None = None, + # constrained_to_indexsets: list[str], + # column_names: list[str] | None = None, + *args, + **kwargs, ) -> BackendModelType: ... def get(self, run_id: int, name: str) -> BackendModelType: ... - def get_by_id(self, id: int) -> BackendModelType: ... - def list( self, *, name: str | None = None, **kwargs ) -> Iterable[BackendModelType]: ... def tabulate(self, *, name: str | None = None, **kwargs) -> pd.DataFrame: ... - def add_data(self, table_id: int, data: dict[str, Any] | pd.DataFrame) -> None: ... + # TODO Not needed as type hint in core layer: + # def get_by_id(self, id: int) -> BackendModelType: ... + + # def add_data( + # self, table_id: int, data: dict[str, Any] | pd.DataFrame + # ) -> None: ... From 5ff2d33730a088cc9259b0e50a3af76d2805e1ce Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 2 Jul 2024 08:55:46 +0200 Subject: [PATCH 4/9] Use abstraction layer for core optimization scalar --- ixmp4/core/optimization/scalar.py | 40 +++++----------------- ixmp4/data/abstract/optimization/scalar.py | 8 ++--- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/ixmp4/core/optimization/scalar.py b/ixmp4/core/optimization/scalar.py index bfd3d196..fd5123c3 100644 --- a/ixmp4/core/optimization/scalar.py +++ b/ixmp4/core/optimization/scalar.py @@ -1,22 +1,20 @@ -from collections.abc import Iterable from datetime import datetime from typing import TYPE_CHECKING, ClassVar if TYPE_CHECKING: - from . import InitKwargs + pass -import pandas as pd # TODO Import this from typing when dropping Python 3.11 -from typing_extensions import Unpack -from ixmp4.core.base import BaseFacade, BaseModelFacade +from ixmp4.core.base import BaseModelFacade from ixmp4.core.unit import Unit from ixmp4.data.abstract import Docs as DocsModel -from ixmp4.data.abstract import Run from ixmp4.data.abstract import Scalar as ScalarModel from ixmp4.data.abstract import Unit as UnitModel +from .base import Lister, Retriever, Tabulator + class Scalar(BaseModelFacade): _model: ScalarModel @@ -99,12 +97,11 @@ def __str__(self) -> str: return f"" -class ScalarRepository(BaseFacade): - _run: Run - - def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: - super().__init__(**kwargs) - self._run = _run +class ScalarRepository(Retriever[Scalar], Lister[Scalar], Tabulator[Scalar]): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._backend_repository = self.backend.optimization.scalars + self._model_type = Scalar def create(self, name: str, value: float, unit: str | Unit | None = None) -> Scalar: if isinstance(unit, Unit): @@ -127,22 +124,3 @@ def create(self, name: str, value: float, unit: str | Unit | None = None) -> Sca "run.optimization.scalars.update()?" ) from e return Scalar(_backend=self.backend, _model=model) - - def get(self, name: str) -> Scalar: - model = self.backend.optimization.scalars.get(run_id=self._run.id, name=name) - return Scalar(_backend=self.backend, _model=model) - - def list(self, name: str | None = None) -> Iterable[Scalar]: - scalars = self.backend.optimization.scalars.list(run_id=self._run.id, name=name) - return [ - Scalar( - _backend=self.backend, - _model=i, - ) - for i in scalars - ] - - def tabulate(self, name: str | None = None) -> pd.DataFrame: - return self.backend.optimization.scalars.tabulate( - run_id=self._run.id, name=name - ) diff --git a/ixmp4/data/abstract/optimization/scalar.py b/ixmp4/data/abstract/optimization/scalar.py index aab3da88..0d0b52d8 100644 --- a/ixmp4/data/abstract/optimization/scalar.py +++ b/ixmp4/data/abstract/optimization/scalar.py @@ -52,11 +52,14 @@ class ScalarRepository( ): docs: DocsRepository - def create(self, name: str, value: float, unit_name: str, run_id: int) -> Scalar: + def create(self, run_id: int, name: str, value: float, unit_name: str) -> Scalar: """Creates a Scalar. Parameters ---------- + run_id : int + The id of the :class:`ixmp4.data.abstract.Run` for which this Scalar is + defined. name : str The name of the Scalar. value : float @@ -64,9 +67,6 @@ def create(self, name: str, value: float, unit_name: str, run_id: int) -> Scalar unit_name : str The name of the :class:`ixmp4.data.abstract.Unit` for which this Scalar is defined. - run_id : int - The id of the :class:`ixmp4.data.abstract.Run` for which this Scalar is - defined. Raises ------ From c1b199f1bdf2a9cdbb751b12c003ee6f37103c26 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 27 Nov 2024 09:32:45 +0100 Subject: [PATCH 5/9] Clean up as suggested by review comment --- ixmp4/core/optimization/base.py | 37 ++------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/ixmp4/core/optimization/base.py b/ixmp4/core/optimization/base.py index 6f281905..89f04292 100644 --- a/ixmp4/core/optimization/base.py +++ b/ixmp4/core/optimization/base.py @@ -1,40 +1,10 @@ -# from typing import Any, ClassVar, Generic, TypeVar from typing import Generic, TypeVar import pandas as pd from ixmp4.core.base import BaseFacade, BaseModelFacade - -# from ixmp4.core.exceptions import IxmpError from ixmp4.data import abstract -# class OptimizationBaseModel(BaseModelFacade): -# _model: abstract.OptimizationBaseModel -# NotFound: ClassVar[type[IxmpError]] -# NotUnique: ClassVar[type[IxmpError]] - -# @property -# def id(self) -> int: -# return self._model.id - -# @property -# def name(self) -> str: -# return self._model.name - -# @property -# def run_id(self) -> int: -# return self._model.run__id - -# @property -# def data(self) -> dict[str, Any]: -# return self._model.data - -# # TODO Stopping here since I'm not convinced of the usefulness. The only benefit I -# # see is that we'd separate functional code from placeholders so that if we need -# # to change functionality for e.g. getting 'data', we only need to do so in one -# # place. Still this feels like a lot of repetition. - - OptimizationModelType = TypeVar("OptimizationModelType", bound=BaseModelFacade) @@ -52,7 +22,7 @@ class Creator(OptimizationBaseRepository[OptimizationModelType], abstract.Creato def create( self, name: str, - # TODO But how do we know show in core layer that e.g. Table needs these? + # TODO But how do we now show in core layer that e.g. Table needs these? # constrained_to_indexsets: list[str], # column_names: list[str] | None = None, *args, @@ -88,7 +58,4 @@ class Enumerator( def enumerate( self, *args, table: bool = False, **kwargs ) -> list[OptimizationModelType] | pd.DataFrame: - if table: - return self.tabulate(*args, **kwargs) - else: - return self.list(*args, **kwargs) + return self.tabulate(*args, **kwargs) if table else self.list(*args, **kwargs) From ddfc85b37e750028635907e750c52b7b99d8bfe1 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 27 Nov 2024 09:44:50 +0100 Subject: [PATCH 6/9] Only tabulate items for relevant run --- ixmp4/core/optimization/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ixmp4/core/optimization/base.py b/ixmp4/core/optimization/base.py index 89f04292..208435f7 100644 --- a/ixmp4/core/optimization/base.py +++ b/ixmp4/core/optimization/base.py @@ -49,7 +49,7 @@ def list(self, name: str | None = None) -> list[OptimizationModelType]: class Tabulator(OptimizationBaseRepository[OptimizationModelType], abstract.Tabulator): def tabulate(self, name: str | None = None) -> pd.DataFrame: - return self._backend_repository.tabulate(name=name) + return self._backend_repository.tabulate(run_id=self._run.id, name=name) class Enumerator( From 63b1a4f29cab84ed7e2a631089b52b6efd204ee1 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 27 Nov 2024 09:45:16 +0100 Subject: [PATCH 7/9] Allow include_data parameter for indexset.tabulate() --- ixmp4/core/optimization/indexset.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ixmp4/core/optimization/indexset.py b/ixmp4/core/optimization/indexset.py index e6e59092..e7fb8cc9 100644 --- a/ixmp4/core/optimization/indexset.py +++ b/ixmp4/core/optimization/indexset.py @@ -7,6 +7,8 @@ # TODO Import this from typing when dropping Python 3.11 +import pandas as pd + from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel from ixmp4.data.abstract import IndexSet as IndexSetModel @@ -87,3 +89,10 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._backend_repository = self.backend.optimization.indexsets self._model_type = IndexSet + + def tabulate( + self, name: str | None = None, include_data: bool = False + ) -> pd.DataFrame: + return self._backend_repository.tabulate( + run_id=self._run.id, name=name, include_data=include_data + ) From 78d6134825cdd65ef7c7c5797c0ad805ec4f8105 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 27 Nov 2024 15:01:09 +0100 Subject: [PATCH 8/9] Add annotations --- ixmp4/core/optimization/base.py | 87 ++++++++++++++------ ixmp4/core/optimization/indexset.py | 23 ++---- ixmp4/core/optimization/scalar.py | 15 ++-- ixmp4/core/optimization/table.py | 14 +++- ixmp4/data/abstract/optimization/base.py | 51 +++++------- ixmp4/data/abstract/optimization/indexset.py | 2 + ixmp4/data/abstract/optimization/scalar.py | 2 + ixmp4/data/abstract/optimization/table.py | 2 + 8 files changed, 115 insertions(+), 81 deletions(-) diff --git a/ixmp4/core/optimization/base.py b/ixmp4/core/optimization/base.py index 208435f7..68fe3b8d 100644 --- a/ixmp4/core/optimization/base.py +++ b/ixmp4/core/optimization/base.py @@ -1,61 +1,94 @@ -from typing import Generic, TypeVar +from typing import TYPE_CHECKING, Generic, TypeVar + +if TYPE_CHECKING: + from . import InitKwargs + import pandas as pd +# TODO Import this from typing when dropping Python 3.11 +from typing_extensions import TypedDict, Unpack + from ixmp4.core.base import BaseFacade, BaseModelFacade from ixmp4.data import abstract -OptimizationModelType = TypeVar("OptimizationModelType", bound=BaseModelFacade) +FacadeOptimizationModelType = TypeVar( + "FacadeOptimizationModelType", bound=BaseModelFacade +) +AbstractOptimizationModelType = TypeVar( + "AbstractOptimizationModelType", bound=abstract.BaseModel +) -class OptimizationBaseRepository(BaseFacade, Generic[OptimizationModelType]): +class OptimizationBaseRepository( + BaseFacade, Generic[FacadeOptimizationModelType, AbstractOptimizationModelType] +): _run: abstract.Run - _backend_repository: abstract.BackendBaseRepository - _model_type: type[OptimizationModelType] + _backend_repository: abstract.BackendBaseRepository[AbstractOptimizationModelType] + _model_type: type[FacadeOptimizationModelType] - def __init__(self, _run: abstract.Run, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + def __init__(self, _run: abstract.Run, **kwargs: Unpack["InitKwargs"]) -> None: + super().__init__(**kwargs) self._run = _run -class Creator(OptimizationBaseRepository[OptimizationModelType], abstract.Creator): +class Creator( + OptimizationBaseRepository[ + FacadeOptimizationModelType, AbstractOptimizationModelType + ], + abstract.Creator, +): def create( - self, - name: str, - # TODO But how do we now show in core layer that e.g. Table needs these? - # constrained_to_indexsets: list[str], - # column_names: list[str] | None = None, - *args, - **kwargs, - ) -> OptimizationModelType: + self, name: str, **kwargs: Unpack["abstract.optimization.base.CreateKwargs"] + ) -> FacadeOptimizationModelType: model = self._backend_repository.create( - *args, - **dict(kwargs, name=name, run_id=self._run.id), + run_id=self._run.id, name=name, **kwargs ) return self._model_type(_backend=self.backend, _model=model) -class Retriever(OptimizationBaseRepository[OptimizationModelType], abstract.Retriever): - def get(self, name: str, *args, **kwargs) -> OptimizationModelType: +class Retriever( + OptimizationBaseRepository[ + FacadeOptimizationModelType, AbstractOptimizationModelType + ], + abstract.Retriever, +): + def get(self, name: str) -> FacadeOptimizationModelType: model = self._backend_repository.get(run_id=self._run.id, name=name) return self._model_type(_backend=self.backend, _model=model) -class Lister(OptimizationBaseRepository[OptimizationModelType], abstract.Lister): - def list(self, name: str | None = None) -> list[OptimizationModelType]: +class Lister( + OptimizationBaseRepository[ + FacadeOptimizationModelType, AbstractOptimizationModelType + ], + abstract.Lister, +): + def list(self, name: str | None = None) -> list[FacadeOptimizationModelType]: models = self._backend_repository.list(run_id=self._run.id, name=name) return [self._model_type(_backend=self.backend, _model=m) for m in models] -class Tabulator(OptimizationBaseRepository[OptimizationModelType], abstract.Tabulator): +class Tabulator( + OptimizationBaseRepository[ + FacadeOptimizationModelType, AbstractOptimizationModelType + ], + abstract.Tabulator, +): def tabulate(self, name: str | None = None) -> pd.DataFrame: return self._backend_repository.tabulate(run_id=self._run.id, name=name) +class EnumerateKwargs(TypedDict, total=False): + name: str | None + + class Enumerator( - Lister[OptimizationModelType], Tabulator[OptimizationModelType], abstract.Enumerator + Lister[FacadeOptimizationModelType, AbstractOptimizationModelType], + Tabulator[FacadeOptimizationModelType, AbstractOptimizationModelType], + abstract.Enumerator, ): def enumerate( - self, *args, table: bool = False, **kwargs - ) -> list[OptimizationModelType] | pd.DataFrame: - return self.tabulate(*args, **kwargs) if table else self.list(*args, **kwargs) + self, table: bool = False, **kwargs: Unpack[EnumerateKwargs] + ) -> list[FacadeOptimizationModelType] | pd.DataFrame: + return self.tabulate(**kwargs) if table else self.list(**kwargs) diff --git a/ixmp4/core/optimization/indexset.py b/ixmp4/core/optimization/indexset.py index e7fb8cc9..d73976c2 100644 --- a/ixmp4/core/optimization/indexset.py +++ b/ixmp4/core/optimization/indexset.py @@ -2,16 +2,15 @@ from typing import TYPE_CHECKING, ClassVar if TYPE_CHECKING: - pass - + from . import InitKwargs # TODO Import this from typing when dropping Python 3.11 - -import pandas as pd +from typing_extensions import Unpack from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel from ixmp4.data.abstract import IndexSet as IndexSetModel +from ixmp4.data.abstract import Run from .base import Creator, Lister, Retriever, Tabulator @@ -83,16 +82,12 @@ def __str__(self) -> str: class IndexSetRepository( - Creator[IndexSet], Retriever[IndexSet], Lister[IndexSet], Tabulator[IndexSet] + Creator[IndexSet, IndexSetModel], + Retriever[IndexSet, IndexSetModel], + Lister[IndexSet, IndexSetModel], + Tabulator[IndexSet, IndexSetModel], ): - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: + super().__init__(_run=_run, **kwargs) self._backend_repository = self.backend.optimization.indexsets self._model_type = IndexSet - - def tabulate( - self, name: str | None = None, include_data: bool = False - ) -> pd.DataFrame: - return self._backend_repository.tabulate( - run_id=self._run.id, name=name, include_data=include_data - ) diff --git a/ixmp4/core/optimization/scalar.py b/ixmp4/core/optimization/scalar.py index fd5123c3..1586c2f4 100644 --- a/ixmp4/core/optimization/scalar.py +++ b/ixmp4/core/optimization/scalar.py @@ -2,14 +2,15 @@ from typing import TYPE_CHECKING, ClassVar if TYPE_CHECKING: - pass - + from . import InitKwargs # TODO Import this from typing when dropping Python 3.11 +from typing_extensions import Unpack from ixmp4.core.base import BaseModelFacade from ixmp4.core.unit import Unit from ixmp4.data.abstract import Docs as DocsModel +from ixmp4.data.abstract import Run from ixmp4.data.abstract import Scalar as ScalarModel from ixmp4.data.abstract import Unit as UnitModel @@ -97,9 +98,13 @@ def __str__(self) -> str: return f"" -class ScalarRepository(Retriever[Scalar], Lister[Scalar], Tabulator[Scalar]): - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) +class ScalarRepository( + Retriever[Scalar, ScalarModel], + Lister[Scalar, ScalarModel], + Tabulator[Scalar, ScalarModel], +): + def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: + super().__init__(_run=_run, **kwargs) self._backend_repository = self.backend.optimization.scalars self._model_type = Scalar diff --git a/ixmp4/core/optimization/table.py b/ixmp4/core/optimization/table.py index 3afb5dc1..9ad75433 100644 --- a/ixmp4/core/optimization/table.py +++ b/ixmp4/core/optimization/table.py @@ -2,13 +2,16 @@ from typing import TYPE_CHECKING, Any, ClassVar if TYPE_CHECKING: - pass + from . import InitKwargs import pandas as pd # TODO Import this from typing when dropping Python 3.11 +from typing_extensions import Unpack + from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel +from ixmp4.data.abstract import Run from ixmp4.data.abstract import Table as TableModel from ixmp4.data.abstract.optimization import Column @@ -86,9 +89,12 @@ def __str__(self) -> str: class TableRepository( - Creator[Table], Retriever[Table], Lister[Table], Tabulator[Table] + Creator[Table, TableModel], + Retriever[Table, TableModel], + Lister[Table, TableModel], + Tabulator[Table, TableModel], ): - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: + super().__init__(_run=_run, **kwargs) self._backend_repository = self.backend.optimization.tables self._model_type = Table diff --git a/ixmp4/data/abstract/optimization/base.py b/ixmp4/data/abstract/optimization/base.py index b10762db..b5018ece 100644 --- a/ixmp4/data/abstract/optimization/base.py +++ b/ixmp4/data/abstract/optimization/base.py @@ -1,26 +1,28 @@ -from typing import Generic, Iterable, Protocol, TypeVar +from collections.abc import Iterable +from typing import TYPE_CHECKING, Generic, Protocol, TypeVar + +if TYPE_CHECKING: + from . import EnumerateKwargs import pandas as pd -# from ixmp4.data import types -from .. import base -from ..docs import DocsRepository +# TODO Import this from typing when dropping Python 3.11 +from typing_extensions import TypedDict, Unpack -# from .column import Column +from ixmp4.data.abstract.unit import Unit +from .. import base +from ..docs import DocsRepository -# TODO Currently not in use -# class OptimizationBaseModel(base.BaseModel, Protocol): -# id: types.Integer -# name: types.String -# data: types.JsonDict -# columns: types.Mapped[list[Column]] -# run__id: types.Integer -# created_at: types.DateTime -# created_by: types.String +BackendModelType = TypeVar("BackendModelType", bound=base.BaseModel, covariant=True) -BackendModelType = TypeVar("BackendModelType", bound=base.BaseModel, covariant=True) +class CreateKwargs(TypedDict, total=False): + value: float + unit: str | Unit | None + # TODO But how do we now show in core layer that e.g. Table needs these? + constrained_to_indexsets: list[str] + column_names: list[str] | None class BackendBaseRepository( @@ -33,26 +35,13 @@ class BackendBaseRepository( docs: DocsRepository def create( - self, - run_id: int, - name: str, - # constrained_to_indexsets: list[str], - # column_names: list[str] | None = None, - *args, - **kwargs, + self, run_id: int, name: str, **kwargs: Unpack["CreateKwargs"] ) -> BackendModelType: ... def get(self, run_id: int, name: str) -> BackendModelType: ... def list( - self, *, name: str | None = None, **kwargs + self, **kwargs: Unpack["EnumerateKwargs"] ) -> Iterable[BackendModelType]: ... - def tabulate(self, *, name: str | None = None, **kwargs) -> pd.DataFrame: ... - - # TODO Not needed as type hint in core layer: - # def get_by_id(self, id: int) -> BackendModelType: ... - - # def add_data( - # self, table_id: int, data: dict[str, Any] | pd.DataFrame - # ) -> None: ... + def tabulate(self, **kwargs: Unpack["EnumerateKwargs"]) -> pd.DataFrame: ... diff --git a/ixmp4/data/abstract/optimization/indexset.py b/ixmp4/data/abstract/optimization/indexset.py index cfacc96b..3a68c483 100644 --- a/ixmp4/data/abstract/optimization/indexset.py +++ b/ixmp4/data/abstract/optimization/indexset.py @@ -12,6 +12,7 @@ from .. import base from ..docs import DocsRepository +from .base import BackendBaseRepository class IndexSet(base.BaseModel, Protocol): @@ -36,6 +37,7 @@ def __str__(self) -> str: class IndexSetRepository( + BackendBaseRepository[IndexSet], base.Creator, base.Retriever, base.Enumerator, diff --git a/ixmp4/data/abstract/optimization/scalar.py b/ixmp4/data/abstract/optimization/scalar.py index 0d0b52d8..7533c690 100644 --- a/ixmp4/data/abstract/optimization/scalar.py +++ b/ixmp4/data/abstract/optimization/scalar.py @@ -19,6 +19,7 @@ class EnumerateKwargs(BaseEnumerateKwargs, HasUnitIdFilter, total=False): ... from .. import base from ..docs import DocsRepository from ..unit import Unit +from .base import BackendBaseRepository class Scalar(base.BaseModel, Protocol): @@ -45,6 +46,7 @@ def __str__(self) -> str: class ScalarRepository( + BackendBaseRepository[Scalar], base.Creator, base.Retriever, base.Enumerator, diff --git a/ixmp4/data/abstract/optimization/table.py b/ixmp4/data/abstract/optimization/table.py index 7c2a71e3..d6721d2b 100644 --- a/ixmp4/data/abstract/optimization/table.py +++ b/ixmp4/data/abstract/optimization/table.py @@ -13,6 +13,7 @@ from .. import base from ..docs import DocsRepository +from .base import BackendBaseRepository from .column import Column @@ -39,6 +40,7 @@ def __str__(self) -> str: class TableRepository( + BackendBaseRepository[Table], base.Creator, base.Retriever, base.Enumerator, From 74e773f5ffa66dd8129fe5deec99d64db61b0274 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 27 Nov 2024 15:23:51 +0100 Subject: [PATCH 9/9] DRY Parameter, Equation, Variable * Spell out signature of create() to give proper type hints --- ixmp4/core/optimization/equation.py | 44 ++++++------------- ixmp4/core/optimization/indexset.py | 3 ++ ixmp4/core/optimization/parameter.py | 44 ++++++------------- ixmp4/core/optimization/table.py | 12 +++++ ixmp4/core/optimization/variable.py | 39 +++++----------- ixmp4/data/abstract/optimization/base.py | 3 +- ixmp4/data/abstract/optimization/equation.py | 2 + ixmp4/data/abstract/optimization/parameter.py | 2 + ixmp4/data/abstract/optimization/variable.py | 2 + 9 files changed, 59 insertions(+), 92 deletions(-) diff --git a/ixmp4/core/optimization/equation.py b/ixmp4/core/optimization/equation.py index 1cb3ed30..2d6e2cdf 100644 --- a/ixmp4/core/optimization/equation.py +++ b/ixmp4/core/optimization/equation.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable from datetime import datetime from typing import TYPE_CHECKING, Any, ClassVar @@ -10,12 +9,14 @@ # TODO Import this from typing when dropping Python 3.11 from typing_extensions import Unpack -from ixmp4.core.base import BaseFacade, BaseModelFacade +from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel from ixmp4.data.abstract import Equation as EquationModel from ixmp4.data.abstract import Run from ixmp4.data.abstract.optimization import Column +from .base import Creator, Lister, Retriever, Tabulator + class Equation(BaseModelFacade): _model: EquationModel @@ -106,12 +107,16 @@ def __str__(self) -> str: return f"" -class EquationRepository(BaseFacade): - _run: Run - +class EquationRepository( + Creator[Equation, EquationModel], + Retriever[Equation, EquationModel], + Lister[Equation, EquationModel], + Tabulator[Equation, EquationModel], +): def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: - super().__init__(**kwargs) - self._run = _run + super().__init__(_run=_run, **kwargs) + self._backend_repository = self.backend.optimization.equations + self._model_type = Equation def create( self, @@ -119,31 +124,8 @@ def create( constrained_to_indexsets: list[str], column_names: list[str] | None = None, ) -> Equation: - model = self.backend.optimization.equations.create( + return super().create( name=name, - run_id=self._run.id, constrained_to_indexsets=constrained_to_indexsets, column_names=column_names, ) - return Equation(_backend=self.backend, _model=model) - - def get(self, name: str) -> Equation: - model = self.backend.optimization.equations.get(run_id=self._run.id, name=name) - return Equation(_backend=self.backend, _model=model) - - def list(self, name: str | None = None) -> Iterable[Equation]: - equations = self.backend.optimization.equations.list( - run_id=self._run.id, name=name - ) - return [ - Equation( - _backend=self.backend, - _model=i, - ) - for i in equations - ] - - def tabulate(self, name: str | None = None) -> pd.DataFrame: - return self.backend.optimization.equations.tabulate( - run_id=self._run.id, name=name - ) diff --git a/ixmp4/core/optimization/indexset.py b/ixmp4/core/optimization/indexset.py index d73976c2..cefe403f 100644 --- a/ixmp4/core/optimization/indexset.py +++ b/ixmp4/core/optimization/indexset.py @@ -91,3 +91,6 @@ def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: super().__init__(_run=_run, **kwargs) self._backend_repository = self.backend.optimization.indexsets self._model_type = IndexSet + + def create(self, name: str) -> IndexSet: + return super().create(name=name) diff --git a/ixmp4/core/optimization/parameter.py b/ixmp4/core/optimization/parameter.py index 1e9fcdaa..25f6f054 100644 --- a/ixmp4/core/optimization/parameter.py +++ b/ixmp4/core/optimization/parameter.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable from datetime import datetime from typing import TYPE_CHECKING, Any, ClassVar @@ -10,12 +9,14 @@ # TODO Import this from typing when dropping Python 3.11 from typing_extensions import Unpack -from ixmp4.core.base import BaseFacade, BaseModelFacade +from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel from ixmp4.data.abstract import Parameter as ParameterModel from ixmp4.data.abstract import Run, Unit from ixmp4.data.abstract.optimization import Column +from .base import Creator, Lister, Retriever, Tabulator + class Parameter(BaseModelFacade): _model: ParameterModel @@ -99,12 +100,16 @@ def __str__(self) -> str: return f"" -class ParameterRepository(BaseFacade): - _run: Run - +class ParameterRepository( + Creator[Parameter, ParameterModel], + Retriever[Parameter, ParameterModel], + Lister[Parameter, ParameterModel], + Tabulator[Parameter, ParameterModel], +): def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: - super().__init__(**kwargs) - self._run = _run + super().__init__(_run=_run, **kwargs) + self._backend_repository = self.backend.optimization.parameters + self._model_type = Parameter def create( self, @@ -112,31 +117,8 @@ def create( constrained_to_indexsets: list[str], column_names: list[str] | None = None, ) -> Parameter: - model = self.backend.optimization.parameters.create( + return super().create( name=name, - run_id=self._run.id, constrained_to_indexsets=constrained_to_indexsets, column_names=column_names, ) - return Parameter(_backend=self.backend, _model=model) - - def get(self, name: str) -> Parameter: - model = self.backend.optimization.parameters.get(run_id=self._run.id, name=name) - return Parameter(_backend=self.backend, _model=model) - - def list(self, name: str | None = None) -> Iterable[Parameter]: - parameters = self.backend.optimization.parameters.list( - run_id=self._run.id, name=name - ) - return [ - Parameter( - _backend=self.backend, - _model=i, - ) - for i in parameters - ] - - def tabulate(self, name: str | None = None) -> pd.DataFrame: - return self.backend.optimization.parameters.tabulate( - run_id=self._run.id, name=name - ) diff --git a/ixmp4/core/optimization/table.py b/ixmp4/core/optimization/table.py index 9ad75433..fb1d6316 100644 --- a/ixmp4/core/optimization/table.py +++ b/ixmp4/core/optimization/table.py @@ -98,3 +98,15 @@ def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: super().__init__(_run=_run, **kwargs) self._backend_repository = self.backend.optimization.tables self._model_type = Table + + def create( + self, + name: str, + constrained_to_indexsets: list[str], + column_names: list[str] | None = None, + ) -> Table: + return super().create( + name=name, + constrained_to_indexsets=constrained_to_indexsets, + column_names=column_names, + ) diff --git a/ixmp4/core/optimization/variable.py b/ixmp4/core/optimization/variable.py index 5c1b4146..2bb7cde8 100644 --- a/ixmp4/core/optimization/variable.py +++ b/ixmp4/core/optimization/variable.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable from datetime import datetime from typing import TYPE_CHECKING, Any, ClassVar @@ -10,12 +9,14 @@ # TODO Import this from typing when dropping Python 3.11 from typing_extensions import Unpack -from ixmp4.core.base import BaseFacade, BaseModelFacade +from ixmp4.core.base import BaseModelFacade from ixmp4.data.abstract import Docs as DocsModel from ixmp4.data.abstract import OptimizationVariable as VariableModel from ixmp4.data.abstract import Run from ixmp4.data.abstract.optimization import Column +from .base import Lister, Retriever, Tabulator + class Variable(BaseModelFacade): _model: VariableModel @@ -110,12 +111,15 @@ def __str__(self) -> str: return f"" -class VariableRepository(BaseFacade): - _run: Run - +class VariableRepository( + Retriever[Variable, VariableModel], + Lister[Variable, VariableModel], + Tabulator[Variable, VariableModel], +): def __init__(self, _run: Run, **kwargs: Unpack["InitKwargs"]) -> None: - super().__init__(**kwargs) - self._run = _run + super().__init__(_run=_run, **kwargs) + self._backend_repository = self.backend.optimization.variables + self._model_type = Variable def create( self, @@ -130,24 +134,3 @@ def create( column_names=column_names, ) return Variable(_backend=self.backend, _model=model) - - def get(self, name: str) -> Variable: - model = self.backend.optimization.variables.get(run_id=self._run.id, name=name) - return Variable(_backend=self.backend, _model=model) - - def list(self, name: str | None = None) -> Iterable[Variable]: - variables = self.backend.optimization.variables.list( - run_id=self._run.id, name=name - ) - return [ - Variable( - _backend=self.backend, - _model=i, - ) - for i in variables - ] - - def tabulate(self, name: str | None = None) -> pd.DataFrame: - return self.backend.optimization.variables.tabulate( - run_id=self._run.id, name=name - ) diff --git a/ixmp4/data/abstract/optimization/base.py b/ixmp4/data/abstract/optimization/base.py index b5018ece..05de64fe 100644 --- a/ixmp4/data/abstract/optimization/base.py +++ b/ixmp4/data/abstract/optimization/base.py @@ -20,8 +20,7 @@ class CreateKwargs(TypedDict, total=False): value: float unit: str | Unit | None - # TODO But how do we now show in core layer that e.g. Table needs these? - constrained_to_indexsets: list[str] + constrained_to_indexsets: str | list[str] | None column_names: list[str] | None diff --git a/ixmp4/data/abstract/optimization/equation.py b/ixmp4/data/abstract/optimization/equation.py index a781cd3c..ee66c4eb 100644 --- a/ixmp4/data/abstract/optimization/equation.py +++ b/ixmp4/data/abstract/optimization/equation.py @@ -13,6 +13,7 @@ from .. import base from ..docs import DocsRepository +from .base import BackendBaseRepository from .column import Column @@ -39,6 +40,7 @@ def __str__(self) -> str: class EquationRepository( + BackendBaseRepository[Equation], base.Creator, base.Retriever, base.Enumerator, diff --git a/ixmp4/data/abstract/optimization/parameter.py b/ixmp4/data/abstract/optimization/parameter.py index 94fa7537..ec6025d6 100644 --- a/ixmp4/data/abstract/optimization/parameter.py +++ b/ixmp4/data/abstract/optimization/parameter.py @@ -13,6 +13,7 @@ from .. import base from ..docs import DocsRepository +from .base import BackendBaseRepository from .column import Column @@ -39,6 +40,7 @@ def __str__(self) -> str: class ParameterRepository( + BackendBaseRepository[Parameter], base.Creator, base.Retriever, base.Enumerator, diff --git a/ixmp4/data/abstract/optimization/variable.py b/ixmp4/data/abstract/optimization/variable.py index 39e86aaf..9a50b9fb 100644 --- a/ixmp4/data/abstract/optimization/variable.py +++ b/ixmp4/data/abstract/optimization/variable.py @@ -12,6 +12,7 @@ from .. import base from ..docs import DocsRepository +from .base import BackendBaseRepository from .column import Column @@ -38,6 +39,7 @@ def __str__(self) -> str: class VariableRepository( + BackendBaseRepository[Variable], base.Creator, base.Retriever, base.Enumerator,