Skip to content

Commit

Permalink
Meta: Implement Platform.remove meta; add tests
Browse files Browse the repository at this point in the history
- implement Platform.remove_meta
- deprecate Scenario.delete_meta (Scenario.remove_meta should be used)
- add more tests
  • Loading branch information
fonfon committed Jul 29, 2020
1 parent 2b33297 commit af481d0
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 13 deletions.
18 changes: 17 additions & 1 deletion ixmp/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,22 @@ def set_meta(self, meta: dict, model: str, scenario: str, version):
run version that meta should be attached to
"""

@abstractmethod
def remove_meta(self, categories: list, model: str, scenario: str,
version: int):
"""Remove meta categories.
Parameters
----------
categories : list of str, meta-categories to remove
model : str, optional
model name that meta should be attached to
scenario : str, optional
scenario name that meta should be attached to
version : int or str, optional
run version that meta should be attached to
"""

@abstractmethod
def set_scenario_meta(self, s: Scenario, name_or_dict, value=None):
"""Set single or multiple scenario meta entries.
Expand Down Expand Up @@ -996,7 +1012,7 @@ def set_scenario_meta(self, s: Scenario, name_or_dict, value=None):
"""

@abstractmethod
def delete_scenario_meta(self, s, name):
def remove_scenario_meta(self, s, name):
"""Remove single or multiple scenario meta entries.
Parameters
Expand Down
17 changes: 16 additions & 1 deletion ixmp/backend/jdbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,17 @@ def set_meta(self, meta: dict, model: str = None, scenario: str = None,
jmeta.put(str(k), v)
self.jobj.setMeta(model, scenario, version, jmeta)

def remove_meta(self, categories, model: str = None, scenario: str = None,
version: int = None):
if not (model or scenario or version):
msg = ('At least one parameter has to be provided out of: '
'model, scenario, version')
raise ValueError(msg)
if version is not None:
version = java.Long(version)
return self.jobj.removeMeta(model, scenario, version,
to_jlist(categories))

def get_scenario_meta(self, s):
return {entry.getKey(): _unwrap(entry.getValue())
for entry in self.jindex[s].getMeta().entrySet()}
Expand All @@ -923,7 +934,11 @@ def set_scenario_meta(self, s, name_or_dict, value=None):

getattr(self.jindex[s], method_name)(name_or_dict, value)

def delete_scenario_meta(self, s, name):
def delete_scenario_meta(self, *args, **kwargs):
# Add DeprecationWarning
return self.remove_scenario_meta(self, *args, **kwargs)

def remove_scenario_meta(self, s, name):
if type(name) == str:
name = [name]
jdata = java.LinkedList()
Expand Down
19 changes: 16 additions & 3 deletions ixmp/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class Platform:
'set_doc',
'get_meta',
'set_meta',
'remove_meta',
]

def __init__(self, name=None, backend=None, **backend_args):
Expand Down Expand Up @@ -1600,15 +1601,27 @@ def set_meta(self, name_or_dict, value=None):
name_or_dict = list(name_or_dict.items())
self._backend('set_scenario_meta', name_or_dict, value)

def delete_meta(self, name):
"""Delete scenario meta.
def delete_meta(self, *args, **kwargs):
"""DEPRECATED: Remove scenario meta.
Parameters
----------
name : str or list of str
Either single meta key or list of keys.
"""
self._backend('delete_scenario_meta', name)
warn('Scenario.delete_meta is deprecated; use Scenario.remove_meta '
'instead', DeprecationWarning)
self.remove_meta(*args, **kwargs)

def remove_meta(self, name):
"""Remove scenario meta.
Parameters
----------
name : str or list of str
Either single meta key or list of keys.
"""
self._backend('remove_scenario_meta', name)

# Input and output
def to_excel(self, path, items=ItemType.SET | ItemType.PAR, max_row=None):
Expand Down
100 changes: 92 additions & 8 deletions ixmp/tests/core/test_meta.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
"""Test meta functionality of ixmp.Platform."""

import copy
import pytest

import ixmp
from ixmp.testing import models


sample_meta = {'sample_string': 3, 'another_string': 'string_value'}
sample_meta = {'sample_int': 3, 'sample_string': 'string_value'}


def test_set_meta_missing_argument(mp):
meta = {'sample_string': 3}
with pytest.raises(ValueError):
mp.set_meta(meta)
mp.set_meta(sample_meta)


def test_set_meta(mp):
meta = {'sample_string': 3}
def test_set_get_meta(mp):
"""ASsert that storing+retrieving meta yields expected values"""
model = models['dantzig']['model']
mp.set_meta(meta, model=model)
mp.set_meta(sample_meta, model=model)
obs = mp.get_meta(model=model)
assert obs == meta
assert obs == sample_meta


def test_unique_meta(mp):
Expand Down Expand Up @@ -54,14 +54,98 @@ def test_unique_meta_model_scenario(mp):
def test_unique_meta_scenario(mp):
"""
When setting a meta key on a specific Scenario run, setting the same key
on an higher level should fail too.
on an higher level (Model or Model+Scenario) should fail.
"""
model = models['dantzig']
scen = ixmp.Scenario(mp, **model)
scen.set_meta(sample_meta)
# add a second scenario and verify that setting Meta for it works
scen2 = ixmp.Scenario(mp, **model, version="new")
scen2.commit('save dummy scenario')
scen2.set_meta(sample_meta)
assert scen2.get_meta() == scen.get_meta()

expected = ("Metadata already contains category")
with pytest.raises(Exception, match=expected):
mp.set_meta(sample_meta, **model)
with pytest.raises(Exception, match=expected):
mp.set_meta(sample_meta, model=model['model'])


def test_meta_partial_overwrite(mp):
meta1 = {'sample_string': 3.0, 'another_string': 'string_value'}
meta2 = {'sample_string': 5.0, 'yet_another_string': 'hello'}
model = models['dantzig']
scen = ixmp.Scenario(mp, **model)
scen.set_meta(meta1)
scen.set_meta(meta2)
expected = copy.copy(meta1)
expected.update(meta2)
obs = scen.get_meta()
assert obs == expected


def test_remove_meta(mp):
meta = {'sample_int': 3.0, 'another_string': 'string_value'}
remove_key = 'another_string'
model = models['dantzig']
mp.set_meta(meta, **model)
mp.remove_meta(remove_key, **model)
expected = copy.copy(meta)
del expected[remove_key]
obs = mp.get_meta(**model)
assert expected == obs


def test_remove_invalid_meta(mp):
"""
Removing nonexisting meta entries or None shouldn't result in any meta
being removed. Providing None should give a ValueError.
"""
model = models['dantzig']
mp.set_meta(sample_meta, **model)
with pytest.raises(ValueError):
mp.remove_meta(None, **model)
mp.remove_meta('nonexisting_category', **model)
mp.remove_meta([], **model)
obs = mp.get_meta(**model)
assert obs == sample_meta


def test_set_and_remove_meta_scenario(mp):
"""
Test partial overwriting and meta deletion on scenario level
"""
meta1 = {'sample_string': 3.0, 'another_string': 'string_value'}
meta2 = {'sample_string': 5.0, 'yet_another_string': 'hello'}
remove_key = 'another_string'
model = models['dantzig']

scen = ixmp.Scenario(mp, **model)
scen.set_meta(meta1)
scen.set_meta(meta2)
expected = copy.copy(meta1)
expected.update(meta2)
obs = scen.get_meta()
assert expected == obs

scen.remove_meta(remove_key)
del expected[remove_key]
obs = scen.get_meta()
assert obs == expected


def test_scenario_delete_meta_warning(mp):
"""Scenario.delete_meta works but raises a deprecation warning"""
model = models['dantzig']
scen = ixmp.Scenario(mp, **model)
meta = {'sample_int': 3, 'sample_string': 'string_value'}
remove_key = 'sample_string'

scen.set_meta(sample_meta)
with pytest.warns(DeprecationWarning):
scen.delete_meta(remove_key)
expected = copy.copy(meta)
del expected[remove_key]
obs = scen.get_meta()
assert obs == expected

0 comments on commit af481d0

Please sign in to comment.