Skip to content

Commit

Permalink
Use own errors for Parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
glatterf42 committed Sep 30, 2024
1 parent f160e9c commit a9e5ac8
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 20 deletions.
4 changes: 3 additions & 1 deletion ixmp4/data/db/optimization/parameter/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sqlalchemy.orm import validates

from ixmp4 import db
from ixmp4.core.exceptions import OptimizationDataValidationError
from ixmp4.data import types
from ixmp4.data.abstract import optimization as abstract

Expand All @@ -14,6 +15,7 @@ class Parameter(base.BaseModel):
# NOTE: These might be mixin-able, but would require some abstraction
NotFound: ClassVar = abstract.Parameter.NotFound
NotUnique: ClassVar = abstract.Parameter.NotUnique
DataInvalid: ClassVar = OptimizationDataValidationError
DeletionPrevented: ClassVar = abstract.Parameter.DeletionPrevented

# constrained_to_indexsets: ClassVar[list[str] | None] = None
Expand All @@ -28,7 +30,7 @@ def validate_data(self, key, data: dict[str, Any]):
del data_to_validate["values"]
del data_to_validate["units"]
_ = utils.validate_data(
key=key,
host=self,
data=data_to_validate,
columns=self.columns,
)
Expand Down
27 changes: 20 additions & 7 deletions ixmp4/data/db/optimization/parameter/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pandas as pd

from ixmp4 import db
from ixmp4.core.exceptions import OptimizationItemUsageError
from ixmp4.data.abstract import optimization as abstract
from ixmp4.data.auth.decorators import guard
from ixmp4.data.db.unit import Unit
Expand All @@ -20,6 +21,8 @@ class ParameterRepository(
):
model_class = Parameter

UsageError = OptimizationItemUsageError

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.docs = ParameterDocsRepository(*args, **kwargs)
Expand Down Expand Up @@ -112,16 +115,20 @@ def create(
if isinstance(constrained_to_indexsets, str):
constrained_to_indexsets = list(constrained_to_indexsets)
if column_names and len(column_names) != len(constrained_to_indexsets):
raise ValueError(
raise self.UsageError(
f"While processing Parameter {name}: \n"
"`constrained_to_indexsets` and `column_names` not equal in length! "
"Please provide the same number of entries for both!"
)
# TODO: activate something like this if each column must be indexed by a unique
# indexset
# if len(constrained_to_indexsets) != len(set(constrained_to_indexsets)):
# raise ValueError("Each dimension must be constrained to a unique indexset!") # noqa
# raise self.UsageError("Each dimension must be constrained to a unique indexset!") # noqa
if column_names and len(column_names) != len(set(column_names)):
raise ValueError("The given `column_names` are not unique!")
raise self.UsageError(
f"While processing Parameter {name}: \n"
"The given `column_names` are not unique!"
)

parameter = super().create(
run_id=run_id,
Expand Down Expand Up @@ -149,13 +156,19 @@ def tabulate(self, *args, **kwargs) -> pd.DataFrame:
@guard("edit")
def add_data(self, parameter_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
if isinstance(data, dict):
data = pd.DataFrame.from_dict(data=data)
try:
data = pd.DataFrame.from_dict(data=data)
except ValueError as e:
raise Parameter.DataInvalid(str(e)) from e

parameter = self.get_by_id(id=parameter_id)

missing_columns = set(["values", "units"]) - set(data.columns)
assert (
not missing_columns
), f"Parameter.data must include the column(s): {', '.join(missing_columns)}!"
if missing_columns:
raise OptimizationItemUsageError(
"Parameter.data must include the column(s): "
f"{', '.join(missing_columns)}!"
)

# Can use a set for now, need full column if we care about order
for unit_name in set(data["units"]):
Expand Down
23 changes: 17 additions & 6 deletions tests/core/test_optimization_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

import ixmp4
from ixmp4.core import IndexSet, Parameter
from ixmp4.core.exceptions import (
OptimizationDataValidationError,
OptimizationItemUsageError,
)

from ..utils import assert_unordered_equality, create_indexsets_for_run

Expand Down Expand Up @@ -60,7 +64,7 @@ def test_create_parameter(self, platform: ixmp4.Platform):
)

# Test mismatch in constrained_to_indexsets and column_names raises
with pytest.raises(ValueError, match="not equal in length"):
with pytest.raises(OptimizationItemUsageError, match="not equal in length"):
_ = run.optimization.parameters.create(
"Parameter 2",
constrained_to_indexsets=[indexset.name],
Expand All @@ -76,7 +80,9 @@ def test_create_parameter(self, platform: ixmp4.Platform):
assert parameter_2.columns[0].name == "Column 1"

# Test duplicate column_names raise
with pytest.raises(ValueError, match="`column_names` are not unique"):
with pytest.raises(
OptimizationItemUsageError, match="`column_names` are not unique"
):
_ = run.optimization.parameters.create(
name="Parameter 3",
constrained_to_indexsets=[indexset.name, indexset.name],
Expand Down Expand Up @@ -149,7 +155,7 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):
)

with pytest.raises(
AssertionError, match=r"must include the column\(s\): values!"
OptimizationItemUsageError, match=r"must include the column\(s\): values!"
):
parameter_2.add(
pd.DataFrame(
Expand All @@ -162,7 +168,7 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):
)

with pytest.raises(
AssertionError, match=r"must include the column\(s\): units!"
OptimizationItemUsageError, match=r"must include the column\(s\): units!"
):
parameter_2.add(
data=pd.DataFrame(
Expand All @@ -176,7 +182,10 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):

# By converting data to pd.DataFrame, we automatically enforce equal length
# of new columns, raises All arrays must be of the same length otherwise:
with pytest.raises(ValueError, match="All arrays must be of the same length"):
with pytest.raises(
OptimizationDataValidationError,
match="All arrays must be of the same length",
):
parameter_2.add(
data={
indexset.name: ["foo", "foo"],
Expand All @@ -186,7 +195,9 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):
},
)

with pytest.raises(ValueError, match="contains duplicate rows"):
with pytest.raises(
OptimizationDataValidationError, match="contains duplicate rows"
):
parameter_2.add(
data={
indexset.name: ["foo", "foo"],
Expand Down
23 changes: 17 additions & 6 deletions tests/data/test_optimization_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import pytest

import ixmp4
from ixmp4.core.exceptions import (
OptimizationDataValidationError,
OptimizationItemUsageError,
)
from ixmp4.data.abstract import Parameter

from ..utils import assert_unordered_equality, create_indexsets_for_run
Expand Down Expand Up @@ -60,7 +64,7 @@ def test_create_parameter(self, platform: ixmp4.Platform):
)

# Test mismatch in constrained_to_indexsets and column_names raises
with pytest.raises(ValueError, match="not equal in length"):
with pytest.raises(OptimizationItemUsageError, match="not equal in length"):
_ = platform.backend.optimization.parameters.create(
run_id=run.id,
name="Parameter 2",
Expand All @@ -78,7 +82,9 @@ def test_create_parameter(self, platform: ixmp4.Platform):
assert parameter_2.columns[0].name == "Column 1"

# Test duplicate column_names raise
with pytest.raises(ValueError, match="`column_names` are not unique"):
with pytest.raises(
OptimizationItemUsageError, match="`column_names` are not unique"
):
_ = platform.backend.optimization.parameters.create(
run_id=run.id,
name="Parameter 3",
Expand Down Expand Up @@ -161,7 +167,7 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):
)

with pytest.raises(
AssertionError, match=r"must include the column\(s\): values!"
OptimizationItemUsageError, match=r"must include the column\(s\): values!"
):
platform.backend.optimization.parameters.add_data(
parameter_id=parameter_2.id,
Expand All @@ -175,7 +181,7 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):
)

with pytest.raises(
AssertionError, match=r"must include the column\(s\): units!"
OptimizationItemUsageError, match=r"must include the column\(s\): units!"
):
platform.backend.optimization.parameters.add_data(
parameter_id=parameter_2.id,
Expand All @@ -190,7 +196,10 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):

# By converting data to pd.DataFrame, we automatically enforce equal length
# of new columns, raises All arrays must be of the same length otherwise:
with pytest.raises(ValueError, match="All arrays must be of the same length"):
with pytest.raises(
OptimizationDataValidationError,
match="All arrays must be of the same length",
):
platform.backend.optimization.parameters.add_data(
parameter_id=parameter_2.id,
data={
Expand All @@ -201,7 +210,9 @@ def test_parameter_add_data(self, platform: ixmp4.Platform):
},
)

with pytest.raises(ValueError, match="contains duplicate rows"):
with pytest.raises(
OptimizationDataValidationError, match="contains duplicate rows"
):
platform.backend.optimization.parameters.add_data(
parameter_id=parameter_2.id,
data={
Expand Down

0 comments on commit a9e5ac8

Please sign in to comment.