Skip to content

Commit

Permalink
Move arg checks from JDBCBackend.init_item() to Scenario
Browse files Browse the repository at this point in the history
- Checking and sanitizing user input is the task of the front-end.
- Consolidate Scenario.init_*() methods, parallel to has_*, *_list.
  • Loading branch information
khaeru committed Nov 17, 2023
1 parent 6e76165 commit af97a7f
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 108 deletions.
20 changes: 3 additions & 17 deletions ixmp/backend/jdbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,10 +908,6 @@ def list_items(self, s, type):
return to_pylist(getattr(self.jindex[s], f"get{type.title()}List")())

def init_item(self, s, type, name, idx_sets, idx_names):
# Generate index-set and index-name lists
if isinstance(idx_sets, set) or isinstance(idx_names, set):
raise TypeError("index dimension must be string or ordered lists")

# Check `idx_sets` against values hard-coded in ixmp_source
try:
sets = _fixed_index_sets(s.scheme)[name]
Expand All @@ -927,21 +923,11 @@ def init_item(self, s, type, name, idx_sets, idx_names):
f"Initialize {type} {name!r} with dimensions {idx_sets} != {sets}"
)

# Convert to Java data structure
# Convert to Java data structures
idx_sets = to_jlist(idx_sets) if len(idx_sets) else None
idx_names = to_jlist(idx_names) if idx_names else idx_sets

# Handle `idx_names`, if any
if idx_names:
if len(idx_names) != len(idx_sets):
raise ValueError(
f"index names {repr(idx_names)} must have same length as index sets"
f" {repr(idx_sets)}"
)
idx_names = to_jlist(idx_names)
else:
idx_names = idx_sets

# Initialize the Item
# Retrieve the method that initializes the Item, something like "initializePar"
func = getattr(self.jindex[s], f"initialize{type.title()}")

# The constructor returns a reference to the Java Item, but these aren't exposed
Expand Down
144 changes: 57 additions & 87 deletions ixmp/core/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,34 +156,6 @@ def _keys(self, name, key_or_keys):
else:
return [str(key_or_keys)]

def init_set(
self,
name: str,
idx_sets: Optional[Sequence[str]] = None,
idx_names: Optional[Sequence[str]] = None,
) -> None:
"""Initialize a new set.
Parameters
----------
name : str
Name of the set.
idx_sets : sequence of str or str, optional
Names of other sets that index this set.
idx_names : sequence of str or str, optional
Names of the dimensions indexed by `idx_sets`.
Raises
------
ValueError
If the set (or another object with the same *name*) already exists.
RuntimeError
If the Scenario is not checked out (see :meth:`~TimeSeries.check_out`).
"""
idx_sets = as_str_list(idx_sets) or []
idx_names = as_str_list(idx_names)
return self._backend("init_item", "set", name, idx_sets, idx_names)

def set(
self, name: str, filters: Optional[Dict[str, Sequence[str]]] = None, **kwargs
) -> Union[List[str], pd.DataFrame]:
Expand Down Expand Up @@ -336,27 +308,6 @@ def remove_set(
else:
self._backend("item_delete_elements", "set", name, self._keys(name, key))

def init_par(
self,
name: str,
idx_sets: Sequence[str],
idx_names: Optional[Sequence[str]] = None,
) -> None:
"""Initialize a new parameter.
Parameters
----------
name : str
Name of the parameter.
idx_sets : sequence of str or str, optional
Names of sets that index this parameter.
idx_names : sequence of str or str, optional
Names of the dimensions indexed by `idx_sets`.
"""
idx_sets = as_str_list(idx_sets) or []
idx_names = as_str_list(idx_names)
return self._backend("init_item", "par", name, idx_sets, idx_names)

def par(
self, name: str, filters: Optional[Dict[str, Sequence[str]]] = None, **kwargs
) -> pd.DataFrame:
Expand Down Expand Up @@ -452,6 +403,62 @@ def has_item(self, name: str, item_type=ItemType.MODEL) -> bool:
#: Check whether the scenario has a variable `name`.
has_var = partialmethod(has_item, item_type=ItemType.VAR)

def init_item(
self,
item_type: ItemType,
name: str,
idx_sets: Optional[Sequence[str]] = None,
idx_names: Optional[Sequence[str]] = None,
):
"""Initialize a new item `name` of type `item_type`.
In general, user code **should** call one of :meth:`.init_set`,
:meth:`.init_par`, :meth:`.init_var`, or :meth:`.init_equ` instead of calling
this method directly.
Parameters
----------
item_type : ItemType
The type of the item.
name : str
Name of the item.
idx_sets : sequence of str or str, optional
Name(s) of index sets for a 1+-dimensional item. If none are given, the item
is scalar (zero dimensional).
idx_names : sequence of str or str, optional
Names of the dimensions indexed by `idx_sets`. If given, they must be the
same length as `idx_sets`.
Raises
------
ValueError
- if `idx_names` are given but do not match the length of `idx_sets`.
- if an item with the same `name`, of any `item_type`, already exists.
RuntimeError
if the Scenario is not checked out (see :meth:`~TimeSeries.check_out`).
"""
idx_sets = as_str_list(idx_sets) or []
idx_names = as_str_list(idx_names)

if idx_names and len(idx_names) != len(idx_sets):
raise ValueError(

Check warning on line 444 in ixmp/core/scenario.py

View check run for this annotation

Codecov / codecov/patch

ixmp/core/scenario.py#L444

Added line #L444 was not covered by tests
f"index names {repr(idx_names)} must have same length as index sets"
f" {repr(idx_sets)}"
)

return self._backend(
"init_item", str(item_type.name).lower(), name, idx_sets, idx_names
)

#: Initialize a new equation.
init_equ = partialmethod(init_item, ItemType.EQU)
#: Initialize a new parameter.
init_par = partialmethod(init_item, ItemType.PAR)
#: Initialize a new set.
init_set = partialmethod(init_item, ItemType.SET)
#: Initialize a new variable.
init_var = partialmethod(init_item, ItemType.VAR)

def list_items(
self, item_type=ItemType.MODEL, indexed_by: Optional[str] = None
) -> List[str]:
Expand Down Expand Up @@ -581,7 +588,7 @@ def add_par(
self._backend("item_set_elements", "par", name, elements)

def init_scalar(self, name: str, val: Real, unit: str, comment=None) -> None:
"""Initialize a new scalar.
"""Initialize a new scalar and set its value.
Parameters
----------
Expand Down Expand Up @@ -650,27 +657,6 @@ def remove_par(self, name: str, key=None) -> None:
else:
self._backend("item_delete_elements", "par", name, self._keys(name, key))

def init_var(
self,
name: str,
idx_sets: Optional[Sequence[str]] = None,
idx_names: Optional[Sequence[str]] = None,
) -> None:
"""Initialize a new variable.
Parameters
----------
name : str
Name of the variable.
idx_sets : sequence of str or str, optional
Name(s) of index sets for a 1+-dimensional variable.
idx_names : sequence of str or str, optional
Names of the dimensions indexed by `idx_sets`.
"""
idx_sets = as_str_list(idx_sets) or []
idx_names = as_str_list(idx_names)
return self._backend("init_item", "var", name, idx_sets, idx_names)

def var(self, name: str, filters=None, **kwargs):
"""Return a dataframe of (filtered) elements for a specific variable.
Expand All @@ -683,22 +669,6 @@ def var(self, name: str, filters=None, **kwargs):
"""
return self._backend("item_get_elements", "var", name, filters)

def init_equ(self, name: str, idx_sets=None, idx_names=None) -> None:
"""Initialize a new equation.
Parameters
----------
name : str
Name of the equation.
idx_sets : sequence of str or str, optional
Name(s) of index sets for a 1+-dimensional variable.
idx_names : sequence of str or str, optional
Names of the dimensions indexed by `idx_sets`.
"""
idx_sets = as_str_list(idx_sets) or []
idx_names = as_str_list(idx_names)
return self._backend("init_item", "equ", name, idx_sets, idx_names)

def equ(self, name: str, filters=None, **kwargs) -> pd.DataFrame:
"""Return a dataframe of (filtered) elements for a specific equation.
Expand Down
17 changes: 13 additions & 4 deletions ixmp/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
from importlib.machinery import ModuleSpec, SourceFileLoader
from importlib.util import find_spec
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, Mapping, Tuple
from typing import (
TYPE_CHECKING,
Dict,
Iterable,
Iterator,
List,
Mapping,
Optional,
Tuple,
)
from urllib.parse import urlparse
from warnings import warn

Expand All @@ -16,7 +25,7 @@

from ixmp.backend import ItemType

if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ixmp import TimeSeries

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -48,8 +57,8 @@ def logger():
return logging.getLogger("ixmp")


def as_str_list(arg, idx_names=None):
"""Convert various *arg* to list of str.
def as_str_list(arg, idx_names: Optional[Iterable[str]] = None):
"""Convert various `arg` to list of str.
Several types of arguments are handled:
Expand Down

0 comments on commit af97a7f

Please sign in to comment.