Skip to content

Commit e9df9ee

Browse files
authored
Enable remaining parameter tests (#86)
* Enable remaining parameter tests * Include optimization parameter api layer (#89) * Bump several dependency versions * Let api/column handle both tables and parameters * Make api-layer tests pass * Include optimization parameter core layer (#90) * Enable parameter core layer and test it * Fix things after rebase * Ensure all intended changes survive the rebase * Adapt data validation function for parameters * Allow tests to pass again
1 parent efd1160 commit e9df9ee

File tree

19 files changed

+926
-302
lines changed

19 files changed

+926
-302
lines changed

ixmp4/core/optimization/data.py

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from ..base import BaseFacade
44
from .indexset import IndexSetRepository
5+
from .parameter import ParameterRepository
56
from .scalar import ScalarRepository
67
from .table import TableRepository
78

@@ -11,11 +12,13 @@ class OptimizationData(BaseFacade):
1112
IndexSet, Table, Variable, etc."""
1213

1314
indexsets: IndexSetRepository
15+
parameters: ParameterRepository
1416
scalars: ScalarRepository
1517
tables: TableRepository
1618

1719
def __init__(self, *args, run: Run, **kwargs) -> None:
1820
super().__init__(*args, **kwargs)
1921
self.indexsets = IndexSetRepository(_backend=self.backend, _run=run)
22+
self.parameters = ParameterRepository(_backend=self.backend, _run=run)
2023
self.scalars = ScalarRepository(_backend=self.backend, _run=run)
2124
self.tables = TableRepository(_backend=self.backend, _run=run)

ixmp4/core/optimization/parameter.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,20 @@ def data(self) -> dict[str, Any]:
3434
def add(self, data: dict[str, Any] | pd.DataFrame) -> None:
3535
"""Adds data to an existing Parameter."""
3636
self.backend.optimization.parameters.add_data(
37-
table_id=self._model.id, data=data
37+
parameter_id=self._model.id, data=data
3838
)
3939
self._model.data = self.backend.optimization.parameters.get(
4040
run_id=self._model.run__id, name=self._model.name
4141
).data
4242

43+
@property
44+
def values(self) -> list:
45+
return self._model.data.get("values", [])
46+
47+
@property
48+
def units(self) -> list:
49+
return self._model.data.get("units", [])
50+
4351
@property
4452
def constrained_to_indexsets(self) -> list[str]:
4553
return [column.indexset.name for column in self._model.columns]

ixmp4/data/abstract/optimization/column.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ class Column(base.BaseModel, Protocol):
1414
"""Unique name of the Column."""
1515
dtype: types.String
1616
"""Type of the Column's data."""
17-
table__id: types.Integer
17+
table__id: types.Mapped[int | None]
1818
"""Foreign unique integer id of a Table."""
19+
parameter__id: types.Mapped[int | None]
20+
"""Foreign unique integer id of a Parameter."""
1921
indexset: types.Mapped[IndexSet]
2022
"""Associated IndexSet."""
2123
constrained_to_indexset: types.Integer

ixmp4/data/abstract/optimization/parameter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def tabulate(self, *, name: str | None = None, **kwargs) -> pd.DataFrame:
170170

171171
# TODO Once present, state how to check which IndexSets are linked and which values
172172
# they permit
173-
def add_data(self, table_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
173+
def add_data(self, parameter_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
174174
r"""Adds data to a Parameter.
175175
176176
The data will be validated with the linked constrained
@@ -183,7 +183,7 @@ def add_data(self, table_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
183183
184184
Parameters
185185
----------
186-
table_id : int
186+
parameter_id : int
187187
The id of the :class:`ixmp4.data.abstract.optimization.Parameter`.
188188
data : dict[str, Any] | pandas.DataFrame
189189
The data to be added.

ixmp4/data/api/optimization/column.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class Column(base.BaseModel):
1414
id: int
1515
name: str
1616
dtype: str
17-
table__id: int
17+
table__id: int | None
18+
parameter__id: int | None
1819
indexset: IndexSet
1920
constrained_to_indexset: int
2021
unique: bool

ixmp4/data/api/optimization/parameter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ def create(
5757
column_names=column_names,
5858
)
5959

60-
def add_data(self, table_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
60+
def add_data(self, parameter_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
6161
if isinstance(data, pd.DataFrame):
6262
# data will always contains str, not only Hashable
6363
data: dict[str, Any] = data.to_dict(orient="list") # type: ignore
6464
kwargs = {"data": data}
6565
self._request(
66-
method="PATCH", path=self.prefix + str(table_id) + "/data/", json=kwargs
66+
method="PATCH", path=self.prefix + str(parameter_id) + "/data/", json=kwargs
6767
)
6868

6969
def get(self, run_id: int, name: str) -> Parameter:

ixmp4/data/db/iamc/base.py

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
Deleter,
88
Enumerator,
99
Lister,
10-
NameMixin,
1110
Retriever,
1211
Selecter,
1312
Tabulator,

ixmp4/data/db/optimization/base.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,14 @@
88
Deleter,
99
Enumerator,
1010
Lister,
11-
OptimizationDataMixin,
12-
OptimizationNameMixin,
1311
Retriever,
14-
RunIDMixin,
1512
Selecter,
1613
Tabulator,
1714
TimestampMixin,
18-
UniqueNameRunIDMixin,
1915
)
2016

2117

22-
class BaseModel(RootBaseModel, OptimizationNameMixin, TimestampMixin):
18+
class BaseModel(RootBaseModel, TimestampMixin):
2319
__abstract__ = True
2420
table_prefix = "optimization_"
2521

ixmp4/data/db/optimization/column/model.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from typing import ClassVar
22

3-
from sqlalchemy import UniqueConstraint
4-
53
from ixmp4 import db
64
from ixmp4.data import types
75
from ixmp4.data.abstract import optimization as abstract
@@ -34,4 +32,4 @@ class Column(base.BaseModel):
3432
# Currently not in use:
3533
unique: types.Boolean = db.Column(db.Boolean, default=True)
3634

37-
__table_args__ = (UniqueConstraint("name", "table__id", "parameter__id"),)
35+
__table_args__ = (db.UniqueConstraint("name", "table__id"),)

ixmp4/data/db/optimization/indexset/model.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .. import base
1010

1111

12-
class IndexSet(base.BaseModel, base.UniqueNameRunIDMixin):
12+
class IndexSet(base.BaseModel):
1313
NotFound: ClassVar = abstract.IndexSet.NotFound
1414
NotUnique: ClassVar = abstract.IndexSet.NotUnique
1515
DeletionPrevented: ClassVar = abstract.IndexSet.DeletionPrevented
+17-10
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
1+
import copy
12
from typing import Any, ClassVar
23

3-
import pandas as pd
44
from sqlalchemy.orm import validates
55

66
from ixmp4 import db
77
from ixmp4.data import types
88
from ixmp4.data.abstract import optimization as abstract
99

10-
from .. import Column, base
10+
from .. import Column, base, utils
1111

1212

13-
class Parameter(base.BaseModel, base.OptimizationDataMixin, base.UniqueNameRunIDMixin):
13+
class Parameter(base.BaseModel):
1414
# NOTE: These might be mixin-able, but would require some abstraction
1515
NotFound: ClassVar = abstract.Parameter.NotFound
1616
NotUnique: ClassVar = abstract.Parameter.NotUnique
1717
DeletionPrevented: ClassVar = abstract.Parameter.DeletionPrevented
1818

1919
# constrained_to_indexsets: ClassVar[list[str] | None] = None
2020

21-
# TODO Same as in table/model.py
22-
columns: types.Mapped[list["Column"]] = db.relationship() # type: ignore
21+
run__id: types.RunId
22+
columns: types.Mapped[list["Column"]] = db.relationship()
23+
data: types.JsonDict = db.Column(db.JsonType, nullable=False, default={})
2324

2425
@validates("data")
2526
def validate_data(self, key, data: dict[str, Any]):
26-
data_frame: pd.DataFrame = pd.DataFrame.from_dict(data)
27-
data_frame_to_validate = data_frame.drop(columns=["values", "units"])
28-
29-
self._validate_data(data_frame=data_frame_to_validate, data=data)
30-
return data_frame.to_dict(orient="list")
27+
data_to_validate = copy.deepcopy(data)
28+
del data_to_validate["values"]
29+
del data_to_validate["units"]
30+
_ = utils.validate_data(
31+
key=key,
32+
data=data_to_validate,
33+
columns=self.columns,
34+
)
35+
return data
36+
37+
__table_args__ = (db.UniqueConstraint("name", "run__id"),)

ixmp4/data/db/optimization/parameter/repository.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def add(
7474
run_id: int,
7575
name: str,
7676
) -> Parameter:
77-
parameter = Parameter(name=name, run__id=run_id, **self.get_creation_info())
77+
parameter = Parameter(name=name, run__id=run_id)
78+
parameter.set_creation_info(auth_context=self.backend.auth_context)
7879
self.session.add(parameter)
7980

8081
return parameter
@@ -154,7 +155,7 @@ def add_data(self, parameter_id: int, data: dict[str, Any] | pd.DataFrame) -> No
154155
missing_columns = set(["values", "units"]) - set(data.columns)
155156
assert (
156157
not missing_columns
157-
), f"Parameter.data must include the column(s): {' ,'.join(missing_columns)}!"
158+
), f"Parameter.data must include the column(s): {', '.join(missing_columns)}!"
158159

159160
# Can use a set for now, need full column if we care about order
160161
for unit_name in set(data["units"]):

0 commit comments

Comments
 (0)