Skip to content

Commit

Permalink
✨ feat: Introduce optimization.variables.delete()
Browse files Browse the repository at this point in the history
  • Loading branch information
glatterf42 committed Feb 11, 2025
1 parent 05f595b commit 2895604
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 3 deletions.
3 changes: 2 additions & 1 deletion ixmp4/core/optimization/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from ixmp4.data.abstract import OptimizationVariable as VariableModel
from ixmp4.data.abstract import Run

from .base import Lister, Retriever, Tabulator
from .base import Deleter, Lister, Retriever, Tabulator


class Variable(BaseModelFacade):
Expand Down Expand Up @@ -105,6 +105,7 @@ def __str__(self) -> str:


class VariableRepository(
Deleter[Variable, VariableModel],
Retriever[Variable, VariableModel],
Lister[Variable, VariableModel],
Tabulator[Variable, VariableModel],
Expand Down
18 changes: 18 additions & 0 deletions ixmp4/data/abstract/optimization/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __str__(self) -> str:
class VariableRepository(
BackendBaseRepository[Variable],
base.Creator,
base.Deleter,
base.Retriever,
base.Enumerator,
Protocol,
Expand Down Expand Up @@ -91,6 +92,23 @@ def create(
"""
...

def delete(self, id: int) -> None:
"""Deletes a Variable.
Parameters
----------
id : int
The unique integer id of the Variable.
Raises
------
:class:`ixmp4.data.abstract.Variable.NotFound`:
If the Variable with `id` does not exist.
:class:`ixmp4.data.abstract.Variable.DeletionPrevented`:
If the Variable with `id` is used in the database, preventing it's deletion.
"""
...

def get(self, run_id: int, name: str) -> Variable:
"""Retrieves a Variable.
Expand Down
4 changes: 4 additions & 0 deletions ixmp4/data/api/optimization/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class VariableDocsRepository(DocsRepository):

class VariableRepository(
base.Creator[Variable],
base.Deleter[Variable],
base.Retriever[Variable],
base.Enumerator[Variable],
abstract.OptimizationVariableRepository,
Expand All @@ -64,6 +65,9 @@ def create(
column_names=column_names,
)

def delete(self, id: int) -> None:
super().delete(id=id)

def add_data(self, variable_id: int, data: dict[str, Any] | pd.DataFrame) -> None:
if isinstance(data, pd.DataFrame):
# data will always contain str, not only Hashable
Expand Down
6 changes: 5 additions & 1 deletion ixmp4/data/db/optimization/variable/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ def validate_data(self, key: Any, data: dict[str, Any]) -> dict[str, Any]:
return data

_variable_indexset_associations: types.Mapped[list[VariableIndexsetAssociation]] = (
db.relationship(back_populates="variable", cascade="all, delete-orphan")
db.relationship(
back_populates="variable",
cascade="all, delete-orphan",
passive_deletes=True,
)
)

_indexsets: db.AssociationProxy[list[IndexSet]] = db.association_proxy(
Expand Down
5 changes: 5 additions & 0 deletions ixmp4/data/db/optimization/variable/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

class VariableRepository(
base.Creator[Variable],
base.Deleter[Variable],
base.Retriever[Variable],
base.Enumerator[Variable],
abstract.VariableRepository,
Expand Down Expand Up @@ -125,6 +126,10 @@ def create(
column_names=column_names,
)

@guard("edit")
def delete(self, id: int) -> None:
super().delete(id=id)

@guard("view")
def list(self, **kwargs: Unpack["base.EnumerateKwargs"]) -> Iterable[Variable]:
return super().list(**kwargs)
Expand Down
9 changes: 9 additions & 0 deletions ixmp4/server/rest/optimization/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,12 @@ def create(
backend: Backend = Depends(deps.get_backend),
) -> OptimizationVariable:
return backend.optimization.variables.create(**variable.model_dump())


@autodoc
@router.delete("/{variable_id}/")
def delete(
variable_id: int,
backend: Backend = Depends(deps.get_backend),
) -> None:
backend.optimization.variables.delete(id=variable_id)
38 changes: 37 additions & 1 deletion tests/core/test_optimization_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_create_variable(self, platform: ixmp4.Platform) -> None:
assert variable.marginals == []

# Test creation with indexset
indexset_1, indexset_2 = tuple(
indexset_1, _ = tuple(
IndexSet(_backend=platform.backend, _model=model)
for model in create_indexsets_for_run(platform=platform, run_id=run.id)
)
Expand Down Expand Up @@ -121,6 +121,42 @@ def test_create_variable(self, platform: ixmp4.Platform) -> None:
assert variable_4.column_names == ["Column 1", "Column 2"]
assert variable_4.indexsets == [indexset_1.name, indexset_1.name]

def test_delete_variable(self, platform: ixmp4.Platform) -> None:
run = platform.runs.create("Model", "Scenario")

variable_1 = run.optimization.variables.create(name="Variable 1")

# Test deletion without linked IndexSets
run.optimization.variables.delete(item=variable_1.name)

assert run.optimization.variables.tabulate().empty

(indexset_1,) = create_indexsets_for_run(
platform=platform, run_id=run.id, amount=1
)
variable_2 = run.optimization.variables.create(
name="Variable 2", constrained_to_indexsets=[indexset_1.name]
)

# TODO How to check that DeletionPrevented is raised? No other object uses
# Variable.id, so nothing could prevent the deletion.

# Test unknown name raises
with pytest.raises(OptimizationVariable.NotFound):
run.optimization.variables.delete(item="does not exist")

# Test normal deletion
run.optimization.variables.delete(item=variable_2.name)

assert run.optimization.variables.tabulate().empty

# Confirm that IndexSet has not been deleted
assert not run.optimization.indexsets.tabulate().empty

# Test that association table rows are deleted
# If they haven't, this would raise DeletionPrevented
run.optimization.indexsets.delete(item=indexset_1.id)

def test_get_variable(self, platform: ixmp4.Platform) -> None:
run = platform.runs.create("Model", "Scenario")
(indexset,) = create_indexsets_for_run(
Expand Down
35 changes: 35 additions & 0 deletions tests/data/test_optimization_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,41 @@ def test_create_variable(self, platform: ixmp4.Platform) -> None:
assert variable_4.column_names == ["Column 1", "Column 2"]
assert variable_4.indexsets == [indexset_1.name, indexset_1.name]

def test_delete_variable(self, platform: ixmp4.Platform) -> None:
run = platform.backend.runs.create("Model", "Scenario")

# Test deletion without linked IndexSets
variable_1 = platform.backend.optimization.variables.create(
run_id=run.id, name="Variable 1"
)
platform.backend.optimization.variables.delete(id=variable_1.id)

assert platform.backend.optimization.variables.tabulate().empty

indexset_1, _ = create_indexsets_for_run(platform=platform, run_id=run.id)
variable_2 = platform.backend.optimization.variables.create(
run_id=run.id, name="Variable 2", constrained_to_indexsets=[indexset_1.name]
)

# TODO How to check that DeletionPrevented is raised? No other object uses
# Variable.id, so nothing could prevent the deletion.

# Test unknown id raises
with pytest.raises(OptimizationVariable.NotFound):
platform.backend.optimization.variables.delete(id=(variable_2.id + 1))

# Test normal deletion
platform.backend.optimization.variables.delete(id=variable_2.id)

assert platform.backend.optimization.variables.tabulate().empty

# Confirm that IndexSet has not been deleted
assert not platform.backend.optimization.indexsets.tabulate().empty

# Test that association table rows are deleted
# If they haven't, this would raise DeletionPrevented
platform.backend.optimization.indexsets.delete(id=indexset_1.id)

def test_get_variable(self, platform: ixmp4.Platform) -> None:
run = platform.backend.runs.create("Model", "Scenario")
(indexset,) = create_indexsets_for_run(
Expand Down

0 comments on commit 2895604

Please sign in to comment.