Skip to content

Commit

Permalink
Refactor the facade API to create/get runs (#35)
Browse files Browse the repository at this point in the history
Co-authored-by: Fridolin Glatter <83776373+glatterf42@users.noreply.github.com>
  • Loading branch information
danielhuppmann and glatterf42 authored Dec 13, 2023
1 parent 205ce00 commit fef0783
Show file tree
Hide file tree
Showing 17 changed files with 104 additions and 100 deletions.
7 changes: 0 additions & 7 deletions ixmp4/core/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
"""

from functools import partial

from ixmp4.conf import settings
from ixmp4.conf.auth import BaseAuth
from ixmp4.core.exceptions import PlatformNotFound
Expand All @@ -36,7 +34,6 @@
from .meta import MetaRepository
from .model import ModelRepository
from .region import RegionRepository
from .run import Run as RunModel
from .run import RunRepository
from .scenario import ScenarioRepository
from .unit import UnitRepository
Expand All @@ -46,8 +43,6 @@ class Platform(object):
"""A modeling platform instance as a connection to a data backend.
Enables the manipulation of data via the `Run` class and `Repository` instances."""

Run: partial[RunModel]

runs: RunRepository
iamc: IamcRepository
models: ModelRepository
Expand Down Expand Up @@ -85,8 +80,6 @@ def __init__(
else:
raise TypeError("__init__() is missing required argument 'name'")

self.Run = partial(RunModel, _backend=self.backend)

self.runs = RunRepository(_backend=self.backend)
self.iamc = IamcRepository(_backend=self.backend)
self.models = ModelRepository(_backend=self.backend)
Expand Down
46 changes: 24 additions & 22 deletions ixmp4/core/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,10 @@ class Run(BaseModelFacade):
NotFound: ClassVar = RunModel.NotFound
NotUnique: ClassVar = RunModel.NotUnique

def __init__(
self,
model: str | None = None,
scenario: str | None = None,
version: int | str | None = None,
**kwargs,
) -> None:
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
if getattr(self, "_model", None) is None:
if (model is None) or (scenario is None):
raise TypeError("`Run` requires `model` and `scenario`")

if version is None:
self._model = self.backend.runs.get_default_version(model, scenario)
elif version == "new":
self._model = self.backend.runs.create(model, scenario)
elif isinstance(version, int):
self._model = self.backend.runs.get(model, scenario, version)
else:
raise ValueError(
"Invalid value for `version`, must be 'new' or integer."
)
self.version = self._model.version

self.version = self._model.version

self.iamc = IamcData(_backend=self.backend, run=self._model)
self._meta = RunMetaFacade(_backend=self.backend, run=self._model)
Expand Down Expand Up @@ -80,6 +61,27 @@ def unset_as_default(self):


class RunRepository(BaseFacade):
def create(
self,
model: str,
scenario: str,
) -> Run:
return Run(
_backend=self.backend, _model=self.backend.runs.create(model, scenario)
)

def get(
self,
model: str,
scenario: str,
version: int | None = None,
) -> Run:
if version is None:
_model = self.backend.runs.get_default_version(model, scenario)
else:
_model = self.backend.runs.get(model, scenario, version)
return Run(_backend=self.backend, _model=_model)

def list(self, default_only: bool = True, **kwargs) -> Iterable[Run]:
return [
Run(_backend=self.backend, _model=r)
Expand Down
4 changes: 2 additions & 2 deletions tests/core/test_iamc.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def test_run_tabulate_with_filter(test_mp, test_data_annual):
add_regions(test_mp, test_data_annual["region"].unique())
add_units(test_mp, test_data_annual["unit"].unique())

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")
run.iamc.add(test_data_annual, type=DataPoint.Type.ANNUAL)
obs = run.iamc.tabulate(
variable={"name": "Primary Energy"}, unit={"name": "EJ/yr"}
Expand All @@ -89,7 +89,7 @@ def do_run_datapoints(test_mp, ixmp_data, type=None, arg_data=None):
add_regions(test_mp, ixmp_data["region"].unique())
add_units(test_mp, ixmp_data["unit"].unique())

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")

# == Full Addition ==
# Save to database
Expand Down
6 changes: 3 additions & 3 deletions tests/core/test_indexset.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@all_platforms
class TestCoreIndexSet:
def test_create_indexset(self, test_mp):
run = test_mp.Run("Model", "Scenario", "new")
run = test_mp.runs.create("Model", "Scenario")
index_set_1 = run.optimization.IndexSet("IndexSet 1")
returned_index_set_1 = run.optimization.IndexSet("IndexSet 1")
assert index_set_1.id == returned_index_set_1.id
Expand All @@ -15,7 +15,7 @@ def test_create_indexset(self, test_mp):
assert index_set_1.id != index_set_2.id

def test_add_elements(self, test_mp):
run = test_mp.Run("Model", "Scenario", "new")
run = test_mp.runs.create("Model", "Scenario")
test_elements = ["foo", "bar"]
index_set_1 = run.optimization.IndexSet("IndexSet 1")
index_set_1.add(test_elements)
Expand All @@ -37,7 +37,7 @@ def test_add_elements(self, test_mp):
assert len(index_set_3.elements) == len(index_set_4.elements)

def test_indexset_docs(self, test_mp):
run = test_mp.Run("Model", "Scenario", "new")
run = test_mp.runs.create("Model", "Scenario")
index_set_1 = run.optimization.IndexSet("IndexSet 1")
docs = "Documentation of IndexSet 1"
index_set_1.docs = docs
Expand Down
20 changes: 10 additions & 10 deletions tests/core/test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

@all_platforms
def test_run_meta(test_mp):
run1 = test_mp.Run("Model", "Scenario", version="new")
run1 = test_mp.runs.create("Model", "Scenario")
run1.set_as_default()

# set and update different types of meta indicators
run1.meta = {"mint": 13, "mfloat": 0.0, "mstr": "foo"}
run1.meta["mfloat"] = -1.9

run2 = test_mp.Run("Model", "Scenario")
run2 = test_mp.runs.get("Model", "Scenario")

# assert meta by run
assert dict(run2.meta) == {"mint": 13, "mfloat": -1.9, "mstr": "foo"}
Expand All @@ -31,7 +31,7 @@ def test_run_meta(test_mp):
# remove all meta indicators and set a new indicator
run1.meta = {"mnew": "bar"}

run2 = test_mp.Run("Model", "Scenario")
run2 = test_mp.runs.get("Model", "Scenario")

# assert meta by run
assert dict(run2.meta) == {"mnew": "bar"}
Expand All @@ -42,7 +42,7 @@ def test_run_meta(test_mp):
pdt.assert_frame_equal(test_mp.meta.tabulate(run_id=1), exp)

del run1.meta["mnew"]
run2 = test_mp.Run("Model", "Scenario")
run2 = test_mp.runs.get("Model", "Scenario")

# assert meta by run
assert dict(run2.meta) == {}
Expand All @@ -52,7 +52,7 @@ def test_run_meta(test_mp):
exp = pd.DataFrame([], columns=["run_id", "key", "value"])
pdt.assert_frame_equal(test_mp.meta.tabulate(run_id=1), exp)

run2 = test_mp.Run("Model 2", "Scenario 2", version="new")
run2 = test_mp.runs.create("Model 2", "Scenario 2")
run1.meta = {"mstr": "baz"}
run2.meta = {"mfloat": 3.1415926535897}

Expand Down Expand Up @@ -94,7 +94,7 @@ def test_run_meta(test_mp):
)
def test_run_meta_numpy(test_mp, npvalue1, pyvalue1, npvalue2, pyvalue2):
"""Test that numpy types are cast to simple types"""
run1 = test_mp.Run("Model", "Scenario", version="new")
run1 = test_mp.runs.create("Model", "Scenario")
run1.set_as_default()

# set multiple meta indicators of same type ("value"-column of numpy-type)
Expand All @@ -110,15 +110,15 @@ def test_run_meta_numpy(test_mp, npvalue1, pyvalue1, npvalue2, pyvalue2):
assert run1.meta["key"] == pyvalue2

# assert that meta values were saved and updated correctly
run2 = test_mp.Run("Model", "Scenario")
run2 = test_mp.runs.get("Model", "Scenario")
assert dict(run2.meta) == {"key": pyvalue2, "other key": "some value"}


@all_platforms
@pytest.mark.parametrize("nonevalue", (None, np.nan))
def test_run_meta_none(test_mp, nonevalue):
"""Test that None-values are handled correctly"""
run1 = test_mp.Run("Model", "Scenario", version="new")
run1 = test_mp.runs.create("Model", "Scenario")
run1.set_as_default()

# set multiple indicators where one value is None
Expand All @@ -127,11 +127,11 @@ def test_run_meta_none(test_mp, nonevalue):
with pytest.raises(KeyError, match="'mnone'"):
run1.meta["mnone"]

assert dict(test_mp.Run("Model", "Scenario").meta) == {"mint": 13}
assert dict(test_mp.runs.get("Model", "Scenario").meta) == {"mint": 13}

# delete indicator via setter
run1.meta["mint"] = nonevalue
with pytest.raises(KeyError, match="'mint'"):
run1.meta["mint"]

assert not dict(test_mp.Run("Model", "Scenario").meta)
assert not dict(test_mp.runs.get("Model", "Scenario").meta)
4 changes: 2 additions & 2 deletions tests/core/test_region.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_delete_region(self, test_mp, test_data_annual):
add_regions(test_mp, test_data_annual["region"].unique())
add_units(test_mp, test_data_annual["unit"].unique())

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")
run.iamc.add(test_data_annual, type=DataPoint.Type.ANNUAL)

with pytest.raises(Region.DeletionPrevented):
Expand Down Expand Up @@ -74,7 +74,7 @@ def test_region_unknown(self, test_mp, test_data_annual):

test_data_annual["region"] = "foo"

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")
with pytest.raises(Region.NotFound):
run.iamc.add(test_data_annual, type=DataPoint.Type.ANNUAL)

Expand Down
25 changes: 17 additions & 8 deletions tests/core/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,27 @@ def _expected_runs_table(*row_default):

@all_platforms
class TestCoreRun:
def test_run_notfound(self, test_mp):
# no Run with that model and scenario name exists
with pytest.raises(Run.NotFound):
_ = test_mp.runs.get("Unknown Model", "Unknown Scenario", version=1)

def test_run_versions(self, test_mp):
run1 = test_mp.Run("Model", "Scenario", version="new")
run2 = test_mp.Run("Model", "Scenario", version="new")
run1 = test_mp.runs.create("Model", "Scenario")
run2 = test_mp.runs.create("Model", "Scenario")

assert run1.id != run2.id

# no default version is assigned, so list & tabulate are empty
with pytest.raises(Run.NoDefaultVersion):
run = test_mp.Run("Model", "Scenario")
_ = test_mp.runs.get("Model", "Scenario")
assert test_mp.runs.list() == []
assert test_mp.runs.tabulate().empty

# getting a specific version works even if no default version is assigned
assert run1.id == test_mp.runs.get("Model", "Scenario", version=1).id

# getting the table and list for all runs works
run_list = test_mp.runs.list(default_only=False)
assert len(run_list) == 2
assert run_list[0].id == run1.id
Expand All @@ -58,12 +67,12 @@ def test_run_versions(self, test_mp):
)

# default version can be retrieved directly
run = test_mp.Run("Model", "Scenario")
run = test_mp.runs.get("Model", "Scenario")
assert run1.id == run.id

# default version can be changed
run2.set_as_default()
run = test_mp.Run("Model", "Scenario")
run = test_mp.runs.get("Model", "Scenario")
assert run2.id == run.id

# list shows changed default version only
Expand All @@ -78,7 +87,7 @@ def test_run_versions(self, test_mp):
# unsetting default means run cannot be retrieved directly
run2.unset_as_default()
with pytest.raises(Run.NoDefaultVersion):
test_mp.Run("Model", "Scenario")
test_mp.runs.get("Model", "Scenario")

# non-default version cannot be again set as un-default
with pytest.raises(IxmpError):
Expand Down Expand Up @@ -135,7 +144,7 @@ def test_run_tabulate_with_filter(self, test_mp, test_data_annual):
add_regions(test_mp, test_data_annual["region"].unique())
add_units(test_mp, test_data_annual["unit"].unique())

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")
run.iamc.add(test_data_annual, type=DataPoint.Type.ANNUAL)
obs = run.iamc.tabulate(
variable={"name": "Primary Energy"}, unit={"name": "EJ/yr"}
Expand All @@ -156,7 +165,7 @@ def do_run_datapoints(test_mp, ixmp_data, type=None, arg_data=None):
add_regions(test_mp, ixmp_data["region"].unique())
add_units(test_mp, ixmp_data["unit"].unique())

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")

# == Full Addition ==
# Save to database
Expand Down
4 changes: 2 additions & 2 deletions tests/core/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def test_delete_unit(self, test_mp, test_data_annual):
add_regions(test_mp, test_data_annual["region"].unique())
add_units(test_mp, test_data_annual["unit"].unique())

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")
run.iamc.add(test_data_annual, type=DataPoint.Type.ANNUAL)

with pytest.raises(Unit.DeletionPrevented):
Expand Down Expand Up @@ -78,7 +78,7 @@ def test_unit_unknown(self, test_mp, test_data_annual):

test_data_annual["unit"] = "foo"

run = test_mp.Run("Model", "Scenario", version="new")
run = test_mp.runs.create("Model", "Scenario")
with pytest.raises(Unit.NotFound):
run.iamc.add(test_data_annual, type=DataPoint.Type.ANNUAL)

Expand Down
4 changes: 2 additions & 2 deletions tests/core/test_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def create_testcase_iamc_variables(test_mp):
],
columns=["region", "variable", "unit", "step_year", "value"],
)
run = test_mp.Run("Model", "Scenario", "new")
run = test_mp.runs.create("Model", "Scenario")
run.iamc.add(variable_data, type=ixmp4.DataPoint.Type.ANNUAL)
run.set_as_default()

Expand All @@ -50,7 +50,7 @@ def test_retrieve_iamc_variable(self, test_mp):
],
columns=["region", "variable", "unit", "step_year", "value"],
)
run = test_mp.Run("Model", "Scenario", "new")
run = test_mp.runs.create("Model", "Scenario")
run.iamc.add(variable_data, type=ixmp4.DataPoint.Type.ANNUAL)
run.set_as_default()

Expand Down
2 changes: 1 addition & 1 deletion tests/data/test_iamc_datapoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test_filtering(test_mp, filter, exp_filter):
add_regions(test_mp, data.region.unique())
add_units(test_mp, data.unit.unique())
# add the data for two different models to test filtering
test_mp.Run(f"model_{i + 1}", f"scen_{i + 1}", version="new").iamc.add(data)
test_mp.runs.create(f"model_{i + 1}", f"scen_{i + 1}").iamc.add(data)

obs = (
test_mp.backend.iamc.datapoints.tabulate(join_parameters=True, **filter)
Expand Down
Loading

0 comments on commit fef0783

Please sign in to comment.