Skip to content

Commit

Permalink
Quickfix for numpy.types as meta indicators (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielhuppmann authored Nov 23, 2023
1 parent 5d16ad6 commit 0304c0b
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 7 deletions.
20 changes: 18 additions & 2 deletions ixmp4/core/run.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import UserDict
from typing import ClassVar, Iterable

import numpy as np
import pandas as pd

from ixmp4.data.abstract import Run as RunModel
Expand Down Expand Up @@ -114,7 +115,10 @@ def _set(self, meta: dict):
df = pd.DataFrame({"key": self.data.keys()})
df["run__id"] = self.run.id
self.backend.meta.bulk_delete(df)
df = pd.DataFrame({"key": meta.keys(), "value": meta.values()})
df = pd.DataFrame(
{"key": meta.keys(), "value": [numpy_to_pytype(v) for v in meta.values()]}
)
df.dropna(axis=0, inplace=True)
df["run__id"] = self.run.id
self.backend.meta.bulk_upsert(df)
self.df, self.data = self._get()
Expand All @@ -125,10 +129,22 @@ def __setitem__(self, key, value: int | float | str | bool):
except KeyError:
pass

self.backend.meta.create(self.run.id, key, value)
value = numpy_to_pytype(value)
if value is not None:
self.backend.meta.create(self.run.id, key, value)
self.df, self.data = self._get()

def __delitem__(self, key):
id = dict(zip(self.df["key"], self.df["id"]))[key]
self.backend.meta.delete(id)
self.df, self.data = self._get()


def numpy_to_pytype(value):
"""Cast numpy-types to basic Python types"""
if value is np.nan: # np.nan is cast to 'float', not None
return None
elif isinstance(value, np.generic):
return value.item()
else:
return value
61 changes: 56 additions & 5 deletions tests/core/test_meta.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import numpy as np
import pandas as pd
import pandas.testing as pdt
import pytest

from ..utils import all_platforms


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

# set and update different types of meta indicators
Expand Down Expand Up @@ -84,3 +82,56 @@ def test_run_meta(test_mp):
run1.meta = {"mstr": "baz", "mfloat": 3.1415926535897}
exp = pd.DataFrame([[1, "mstr", "baz"]], columns=["run_id", "key", "value"])
pdt.assert_frame_equal(test_mp.meta.tabulate(key="mstr"), exp)


@all_platforms
@pytest.mark.parametrize(
"npvalue1, pyvalue1, npvalue2, pyvalue2",
[
(np.int64(1), 1, np.int64(13), 13),
(np.float64(1.9), 1.9, np.float64(13.9), 13.9),
],
)
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.set_as_default()

# set multiple meta indicators of same type ("value"-column of numpy-type)
run1.meta = {"key": npvalue1, "other key": npvalue1}
assert run1.meta["key"] == pyvalue1

# set meta indicators of different types ("value"-column of type `object`)
run1.meta = {"key": npvalue1, "other key": "some value"}
assert run1.meta["key"] == pyvalue1

# set meta via setter
run1.meta["key"] = npvalue2
assert run1.meta["key"] == pyvalue2

# assert that meta values were saved and updated correctly
run2 = test_mp.Run("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.set_as_default()

# set multiple indicators where one value is None
run1.meta = {"mint": 13, "mnone": nonevalue}
assert run1.meta["mint"] == 13
with pytest.raises(KeyError, match="'mnone'"):
run1.meta["mnone"]

assert dict(test_mp.Run("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)

0 comments on commit 0304c0b

Please sign in to comment.