Skip to content

Commit

Permalink
Merge branch 'metno:main-dev' into andrear_units
Browse files Browse the repository at this point in the history
  • Loading branch information
andrearosendahl authored Nov 22, 2024
2 parents 297062c + e36edb9 commit 198937c
Show file tree
Hide file tree
Showing 32 changed files with 379 additions and 291 deletions.
51 changes: 1 addition & 50 deletions pyaerocom/_lowlevel_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def _class_name(obj):
return type(obj).__name__


# TODO: Check to see if instances of these classes can instead use pydantic
class Validator(abc.ABC):
def __set_name__(self, owner, name):
self._name = name
Expand Down Expand Up @@ -113,56 +114,6 @@ def validate(self, val):
return val


class StrWithDefault(Validator):
def __init__(self, default: str):
self.default = default

def validate(self, val):
if not isinstance(val, str):
if val is None:
val = self.default
else:
raise ValueError(f"need str or None, got {val}")
return val


class FlexList(Validator):
"""list that can be instantated via input str, tuple or list or None"""

def validate(self, val):
if isinstance(val, str):
val = [val]
elif isinstance(val, tuple):
val = list(val)
elif val is None:
val = []
elif not isinstance(val, list):
raise ValueError(f"failed to convert {val} to list")
return val


class EitherOf(Validator):
_allowed = FlexList()

def __init__(self, allowed: list):
self._allowed = allowed

def validate(self, val):
if not any([x == val for x in self._allowed]):
raise ValueError(f"invalid value {val}, needs to be either of {self._allowed}.")
return val


class ListOfStrings(FlexList):
def validate(self, val):
# make sure to have a list
val = super().validate(val)
# make sure all entries are strings
if not all([isinstance(x, str) for x in val]):
raise ValueError(f"not all items are str type in input list {val}")
return val


class Loc(abc.ABC):
"""Abstract descriptor representing a path location
Expand Down
68 changes: 32 additions & 36 deletions pyaerocom/aeroval/aux_io_helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import importlib
import os
import sys
from collections.abc import Callable

from pyaerocom._lowlevel_helpers import AsciiFileLoc, ListOfStrings
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self

from typing import TYPE_CHECKING

from pydantic import (
BaseModel,
model_validator,
)

from pyaerocom._lowlevel_helpers import AsciiFileLoc


def check_aux_info(fun, vars_required, funcs):
Expand All @@ -26,11 +39,11 @@ def check_aux_info(fun, vars_required, funcs):
required.
"""
spec = _AuxReadSpec(fun, vars_required, funcs)
spec = _AuxReadSpec(fun=fun, vars_required=vars_required, funcs=funcs)
return dict(fun=spec.fun, vars_required=spec.vars_required)


class _AuxReadSpec:
class _AuxReadSpec(BaseModel):
"""
Class that specifies requirements for computation of additional variables
Expand All @@ -53,39 +66,22 @@ class _AuxReadSpec:
"""

vars_required = ListOfStrings()

def __init__(self, fun, vars_required: list, funcs: dict):
self.vars_required = vars_required
self.fun = self.get_func(fun, funcs)

def get_func(self, fun, funcs):
"""
Get callable function for computation of variable
Parameters
----------
fun : str or callable
Name of function or function.
funcs : dict
Dictionary with possible functions (values) and names (keys)
Raises
------
ValueError
If function could not be retrieved.
Returns
-------
callable
callable function object.
"""
if callable(fun):
return fun
elif isinstance(fun, str):
return funcs[fun]
raise ValueError("failed to retrieve aux func")
if TYPE_CHECKING:
fun: Callable
else:
fun: str | Callable
vars_required: list[str]
funcs: dict[str, Callable]

@model_validator(mode="after")
def validate_fun(self) -> Self:
if callable(self.fun):
return self
elif isinstance(self.fun, str):
self.fun = self.funcs[self.fun]
return self
else:
raise ValueError("failed to retrieve aux func")


class ReadAuxHandler:
Expand Down
20 changes: 19 additions & 1 deletion pyaerocom/aeroval/coldatatojson_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ def process_coldata(self, coldata: ColocatedData):
if "var_name_input" in coldata.metadata:
obs_var = coldata.metadata["var_name_input"][0]
model_var = coldata.metadata["var_name_input"][1]
elif (
"obs_vars" in coldata.metadata
): # Try and get from obs_vars. Should not be needed in reading in a ColocatedData object created with pyaerocom.
obs_var = model_var = coldata.metadata["obs_vars"]
coldata.metadata["var_name_input"] = [obs_var, model_var]
logger.warning(
"ColdataToJsonEngine: Failed to access var_name_input from coldata.metadata. "
"This could be because you're using a ColocatedData object created outside of pyaerocom. "
"Setting obs_var and model_data to value in obs_vars instead. "
"Setting var_input_data to these values as well. "
)

else:
obs_var = model_var = "UNDEFINED"

Expand Down Expand Up @@ -369,7 +381,13 @@ def _process_stats_timeseries_for_all_regions(

region = regnames[reg]
self.exp_output.add_heatmap_timeseries_entry(
stats_ts, region, obs_name, var_name_web, vert_code, model_name, model_var
stats_ts,
region,
obs_name,
var_name_web,
vert_code,
model_name,
model_var,
)

logger.info("Processing heatmap data for all regions")
Expand Down
Loading

0 comments on commit 198937c

Please sign in to comment.