Skip to content

Commit

Permalink
Add complex filters for RunMetaEntries (#26)
Browse files Browse the repository at this point in the history
* split meta.py into model and repo

* add __init__.py

* add filter

* add base filter

* adjust abstract signature and docs

* export meta base filter

* adjust api and api client

* adjust core api

* adjust tests

* add PATCH query endpoint

* fix bug for non-default run meta entries

* fix unused import

* Fix invalid-escape-sequences

* Add tests for meta-default-run filters

* Add tests for meta-key filters

---------

Co-authored-by: Fridolin Glatter <glatter@iiasa.ac.at>
Co-authored-by: Pino Mussak <mussak@iiasa.ac.at>
  • Loading branch information
3 people committed Nov 14, 2023
1 parent c482ef4 commit 64b65e3
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 132 deletions.
2 changes: 1 addition & 1 deletion doc/source/openapi-v1.json

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions ixmp4/core/meta.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
from typing import List

import pandas as pd

from .base import BaseFacade


class MetaRepository(BaseFacade):
def tabulate(self, run_ids: List[int]) -> pd.DataFrame:
def tabulate(self, **kwargs) -> pd.DataFrame:
# TODO: accept list of `Run` instances as arg
# TODO: expand run-id to model-scenario-version-id columns
return (
self.backend.meta.tabulate(run_ids=run_ids)
self.backend.meta.tabulate(**kwargs)
.drop(columns=["id", "type"])
.rename(columns={"run__id": "run_id"})
)
2 changes: 1 addition & 1 deletion ixmp4/core/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def __init__(self, run: RunModel, *args, **kwargs) -> None:
self.df, self.data = self._get()

def _get(self) -> tuple[pd.DataFrame, dict]:
df = self.backend.meta.tabulate(run_ids=[self.run.id])
df = self.backend.meta.tabulate(run_id=self.run.id, run={"default_only": False})
if df.empty:
return df, {}
return df, dict(zip(df["key"], df["value"]))
Expand Down
25 changes: 8 additions & 17 deletions ixmp4/data/abstract/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,14 @@ def delete(self, id: int) -> None:

def list(
self,
*,
run_ids: Iterable[int] | None = None,
keys: Iterable[str] | None = None,
**kwargs,
) -> Iterable[RunMetaEntry]:
"""Lists run's meta indicator entries by specified criteria.
r"""Lists run's meta indicator entries by specified criteria.
Parameters
----------
run_ids : list of int
A list of run ids.
keys : list of str
A list of string keys.
\*\*kwargs: any
Filter parameters as specified in `ixmp4.data.db.meta.filter.RunMetaEntryFilter`.
Returns
-------
Expand All @@ -148,19 +144,14 @@ def list(

def tabulate(
self,
*,
run_ids: Iterable[int] | None = None,
keys: Iterable[str] | None = None,
**kwargs,
) -> pd.DataFrame:
"""Tabulates run's meta indicator entries by specified criteria.
r"""Tabulates run's meta indicator entries by specified criteria.
Parameters
----------
run_ids : list of int
A list of run ids.
keys : list of str
A list of string keys.
\*\*kwargs: any
Filter parameters as specified in `ixmp4.data.db.meta.filter.RunMetaEntryFilter`.
Returns
-------
Expand Down
8 changes: 4 additions & 4 deletions ixmp4/data/abstract/optimization/indexset.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ def get(self, run_id: int, name: str) -> IndexSet:
...

def list(self, *, name: str | None = None, **kwargs) -> Iterable[IndexSet]:
"""Lists IndexSets by specified criteria.
r"""Lists IndexSets by specified criteria.
Parameters
----------
name : str
The name of an IndexSet. If supplied only one result will be returned.
# TODO: Update kwargs
**kwargs: any
\*\*kwargs: any
More filter parameters as specified in
`ixmp4.data.db.iamc.variable.filters.VariableFilter`.
Expand All @@ -98,14 +98,14 @@ def list(self, *, name: str | None = None, **kwargs) -> Iterable[IndexSet]:
...

def tabulate(self, *, name: str | None = None, **kwargs) -> pd.DataFrame:
"""Tabulate IndexSets by specified criteria.
r"""Tabulate IndexSets by specified criteria.
Parameters
----------
name : str
The name of an IndexSet. If supplied only one result will be returned.
# TODO: Update kwargs
**kwargs: any
\*\*kwargs: any
More filter parameters as specified in
`ixmp4.data.db.iamc.variable.filters.VariableFilter`.
Expand Down
3 changes: 2 additions & 1 deletion ixmp4/data/api/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class RunMetaEntryRepository(
):
model_class = RunMetaEntry
prefix = "meta/"
enumeration_method = "PATCH"

def create(
self,
Expand All @@ -40,7 +41,7 @@ def create(
return super().create(run__id=run__id, key=key, value=value)

def get(self, run__id: int, key: str) -> RunMetaEntry:
return super().get(run__ids=[run__id], keys=[key])
return super().get(run_id=run__id, key=key)

def delete(self, id: int) -> None:
super().delete(id)
Expand Down
1 change: 1 addition & 0 deletions ixmp4/data/db/filters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .meta import RunMetaEntryFilter
from .model import ModelFilter
from .optimizationindexset import OptimizationIndexSetFilter
from .region import RegionFilter
Expand Down
23 changes: 23 additions & 0 deletions ixmp4/data/db/filters/meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import ClassVar

from ixmp4.db import filters

from .. import RunMetaEntry


class RunMetaEntryFilter(filters.BaseFilter, metaclass=filters.FilterMeta):
sqla_model: ClassVar[type] = RunMetaEntry

id: filters.Id
type: filters.String
run__id: filters.Integer = filters.Field(None, alias="run_id")

key: filters.String

value_int: filters.Integer
value_str: filters.String
value_float: filters.Float
value_bool: filters.Boolean

def join(self, exc, **kwargs):
return exc
2 changes: 2 additions & 0 deletions ixmp4/data/db/meta/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .model import RunMetaEntry
from .repository import RunMetaEntryRepository
19 changes: 19 additions & 0 deletions ixmp4/data/db/meta/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from ixmp4.data.db import filters as base
from ixmp4.data.db.run import Run
from ixmp4.db import filters, utils

from .model import RunMetaEntry


class RunFilter(base.RunFilter, metaclass=filters.FilterMeta):
def join(self, exc, **kwargs):
if not utils.is_joined(exc, Run):
return exc.join(Run, onclause=RunMetaEntry.run__id == Run.id)
else:
return exc


class RunMetaEntryFilter(base.RunMetaEntryFilter, metaclass=filters.FilterMeta):
run: RunFilter = filters.Field(
default=RunFilter(id=None, version=None, is_default=True)
)
76 changes: 76 additions & 0 deletions ixmp4/data/db/meta/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from typing import ClassVar

from ixmp4 import db
from ixmp4.core.exceptions import InvalidRunMeta
from ixmp4.data import abstract, types

from .. import base


class RunMetaEntry(base.BaseModel):
NotFound: ClassVar = abstract.RunMetaEntry.NotFound
NotUnique: ClassVar = abstract.RunMetaEntry.NotUnique
DeletionPrevented: ClassVar = abstract.RunMetaEntry.DeletionPrevented

Type: ClassVar = abstract.RunMetaEntry.Type

_column_map = {
abstract.RunMetaEntry.Type.INT: "value_int",
abstract.RunMetaEntry.Type.STR: "value_str",
abstract.RunMetaEntry.Type.FLOAT: "value_float",
abstract.RunMetaEntry.Type.BOOL: "value_bool",
}

__table_args__ = (
db.UniqueConstraint(
"run__id",
"key",
),
)
updateable_columns = [
"type",
"value_int",
"value_str",
"value_float",
"value_bool",
]

run__id: types.Integer = db.Column(
db.Integer,
db.ForeignKey("run.id"),
nullable=False,
index=True,
)
run = db.relationship(
"Run",
backref="meta",
foreign_keys=[run__id],
)

key: types.String = db.Column(db.String(1023), nullable=False)
type: types.String = db.Column(db.String(20), nullable=False)

value_int: types.Integer = db.Column(db.Integer, nullable=True)
value_str: types.String = db.Column(db.String(1023), nullable=True)
value_float: types.Float = db.Column(db.Float, nullable=True)
value_bool: types.Boolean = db.Column(db.Boolean, nullable=True)

@property
def value(self) -> abstract.MetaValue:
type_ = RunMetaEntry.Type(self.type)
col = self._column_map[type_]
return getattr(self, col)

def __init__(self, *args, **kwargs) -> None:
value = kwargs.pop("value")
value_type = type(value)
try:
type_ = RunMetaEntry.Type.from_pytype(value_type)
col = self._column_map[type_]
except KeyError:
raise InvalidRunMeta(
f"Invalid type `{value_type}` for value of `RunMetaEntry`."
)
kwargs["type"] = type_
kwargs[col] = value
super().__init__(*args, **kwargs)

0 comments on commit 64b65e3

Please sign in to comment.