Skip to content

Commit

Permalink
Enable simple region checking for cli (#664)
Browse files Browse the repository at this point in the history
Co-authored-by: hboisgon <[email protected]>
  • Loading branch information
savente93 and hboisgon authored Dec 1, 2023
1 parent 96e23a0 commit 0bf8e2a
Show file tree
Hide file tree
Showing 12 changed files with 328 additions and 359 deletions.
6 changes: 3 additions & 3 deletions data/catalogs/deltares_data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ ghs_pop_2015_4326_v2019a:
path: socio_economic/ghs/GHS_POP_E2015_GLOBE_R2019A_4326_9ss_V1_0.tif

ghs_pop_2015_54009_v2019a:
crs: 54009
crs: "ESRI:54009"
data_type: RasterDataset
driver: raster
kwargs:
Expand All @@ -779,7 +779,7 @@ ghs_pop_2015_54009_v2019a:
ghs_smod:
alias: ghs_smod_2015_54009_v2019a
ghs_smod_2015_54009_v2016a:
crs: 54009
crs: "ESRI:54009"
data_type: RasterDataset
driver: raster
kwargs:
Expand All @@ -796,7 +796,7 @@ ghs_smod_2015_54009_v2016a:
path: socio_economic/ghs/GHS_SMOD_POP2015_GLOBE_R2016A_54009_1k_v1_0.tif

ghs_smod_2015_54009_v2019a:
crs: 54009
crs: "ESRI:54009"
data_type: RasterDataset
driver: raster
kwargs:
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Added
-----
- Export CLI now also accepts time tuples (#660)
- New stats.skills VE and RSR (#666)
- Check CLI command can now validate bbox and geom regions (#664)

Changed
-------
Expand Down
4 changes: 3 additions & 1 deletion docs/user_guide/data_overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ However, we do plan to expand its functionality over time. It can be use for exa

.. code-block:: console
hydromt check grid -d /path/to/data_catalog.yml -i /path/to/model_config.yml
hydromt check grid -d /path/to/data_catalog.yml -i /path/to/model_config.yml -r '{'bbox': [-1,-1,1,1]}'
currently only `bbox` and `geom` variants of regions are supported in validation. Also note that the geom variant will only check whether the file exists not its contents. We also plan to expand this functionality in the future.

.. _get_data_python:

Expand Down
65 changes: 45 additions & 20 deletions hydromt/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

from hydromt.data_catalog import DataCatalog
from hydromt.validators.data_catalog import DataCatalogValidator
from hydromt.validators.model_config import HydromtModelStep
from hydromt.validators.model_config import HydromtModelSetup
from hydromt.validators.region import validate_region

from .. import __version__, log
from ..models import MODELS
Expand Down Expand Up @@ -332,39 +333,51 @@ def update(


@main.command(
short_help="Validate config files are correct",
short_help="Validate config / data catalog / region",
)
@click.argument(
"MODEL",
@click.option(
"-m",
"--model",
type=str,
default=None,
help="Model name, e.g. wflow, sfincs, etc. to validate config file.",
)
@opt_config
@data_opt
@deltares_data_opt
@quiet_opt
@verbose_opt
@region_opt
@click.pass_context
def check(
ctx,
model,
config,
data,
dd,
region: Optional[Dict[Any, Any]],
quiet: int,
verbose: int,
):
"""Verify that provided data catalog files are in the correct format.
"""
Verify that provided data catalog and config files are in the correct format.
Additionnaly region bbox and geom can also be validated.
Example usage:
--------------
hydromt check grid -d /path/to/data_catalog.yml -i /path/to/model_config.yml
Check data catalog file:
hydromt check -d /path/to/data_catalog.yml -v
Check data catalog and grid_model config file:
hydromt check -m grid_model -d /path/to/data_catalog.yml -i /path/to/model_config.yml -v
With region:
hydromt check -m grid_model -d /path/to/data_catalog.yml -i /path/to/model_config.yml -r '{'bbox': [-1,-1,1,1]}' -v
""" # noqa: E501
# logger
log_level = max(10, 30 - 10 * (verbose - quiet))
logger = log.setuplog("check", join(".", "hydromt.log"), log_level=log_level)
logger.info(f"Output dir: {export_dest_path}")
try:
all_exceptions = []
for cat_path in data:
Expand All @@ -376,24 +389,36 @@ def check(
all_exceptions.append(e)
logger.info("Catalog has errors")

mod = MODELS.load(model)
try:
config_dict = cli_utils.parse_config(config)
logger.info(f"Validating config at {config}")
if region:
logger.info(f"Validating region {region}")
try:
validate_region(region)
logger.info("Region is valid!")

except (ValidationError, ValueError, NotImplementedError) as e:
logger.info("region has errors")
all_exceptions.append(e)

HydromtModelStep.from_dict(config_dict, model=mod)
logger.info("Model config valid!")
if config:
mod = MODELS.load(model)
logger.info(f"Validating for model {model} of type {type(mod).__name__}")
try:
config_dict = cli_utils.parse_config(config)
logger.info(f"Validating config at {config}")

except (ValidationError, ValueError) as e:
logger.info("Model has errors")
all_exceptions.append(e)
HydromtModelSetup.from_dict(config_dict, model=mod)
logger.info("Model config valid!")

except (ValidationError, ValueError) as e:
logger.info("Model has errors")
all_exceptions.append(e)

if len(all_exceptions) > 0:
raise Exception(all_exceptions)
raise ValueError(all_exceptions)

except Exception as e:
logger.exception(e) # catch and log errors
raise
raise e
finally:
for handler in logger.handlers[:]:
handler.close()
Expand Down
35 changes: 6 additions & 29 deletions hydromt/validators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,11 @@
DataCatalogMetaData,
DataCatalogValidator,
)
from .model_config import HydromtModelStep
from .model_config import HydromtModelSetup, HydromtModelStep
from .region import (
BoundingBoxBasinRegion,
BoundingBoxInterBasinRegion,
BoundingBoxRegion,
BoundingBoxSubBasinRegion,
GeometryBasinRegion,
GeometryInterBasinRegion,
GeometryRegion,
GeometrySubBasinRegion,
GridRegion,
MeshRegion,
MultiPointBasinRegion,
MultiPointSubBasinRegion,
PointBasinRegion,
PointSubBasinRegion,
WGS84Point,
PathRegion,
Region,
validate_region,
)

Expand All @@ -31,21 +19,10 @@
"DataCatalogItemMetadata",
"DataCatalogMetaData",
"DataCatalogValidator",
"BoundingBoxBasinRegion",
"BoundingBoxInterBasinRegion",
"BoundingBoxRegion",
"BoundingBoxSubBasinRegion",
"GeometryBasinRegion",
"GeometryInterBasinRegion",
"GeometryRegion",
"GeometrySubBasinRegion",
"GridRegion",
"MeshRegion",
"MultiPointBasinRegion",
"MultiPointSubBasinRegion",
"PointBasinRegion",
"PointSubBasinRegion",
"WGS84Point",
"PathRegion",
"Region",
"validate_region",
"HydromtModelStep",
"HydromtModelSetup",
]
51 changes: 44 additions & 7 deletions hydromt/validators/data_catalog.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Pydantic models for the validation of Data catalogs."""
from pathlib import Path
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, List, Literal, Optional, Union

from pydantic import AnyUrl, BaseModel, ConfigDict
from pydantic import AnyUrl, BaseModel, ConfigDict, model_validator
from pydantic.fields import Field
from pydantic_core import Url
from pyproj import CRS
from pyproj.exceptions import CRSError

from hydromt.data_catalog import _yml_from_uri_or_path
from hydromt.typing import Bbox, Number, TimeRange
Expand All @@ -23,6 +25,17 @@ def from_dict(input_dict):
return SourceSpecDict(**input_dict)


class SourceVariant(BaseModel):
"""A variant for a data source."""

provider: Optional[Literal["local", "aws", "gcs"]] = None
version: Optional[Union[str, Number]] = None
path: Path
rename: Optional[Dict[str, str]] = None
filesystem: Optional[Literal["local", "s3", "gcs"]] = None
storage_options: Optional[Dict[str, Any]] = None


class Extent(BaseModel):
"""A validation model for describing the space and time a dataset covers."""

Expand All @@ -34,7 +47,7 @@ class DataCatalogMetaData(BaseModel):
"""The metadata section of a Hydromt data catalog."""

root: Optional[Path] = None
version: Optional[Union[str, int]] = None
version: Optional[Union[str, Number]] = None
name: Optional[str] = None
model_config: ConfigDict = ConfigDict(
str_strip_whitespace=True,
Expand Down Expand Up @@ -81,24 +94,48 @@ class DataCatalogItem(BaseModel):
"""A validated data source."""

name: str
data_type: str
driver: str
path: Path
crs: Optional[int] = None
data_type: Literal["RasterDataset", "GeoDataset", "GeoDataFrame", "DataFrame"]
driver: Literal[
"csv",
"fwf",
"netcdf",
"parquet",
"raster",
"raster_tindex",
"vector",
"vector_table",
"xls",
"xlsx",
"zarr",
]
path: Optional[Path] = None
crs: Optional[Union[int, str]] = None
filesystem: Optional[str] = None
kwargs: Dict[str, Any] = Field(default_factory=dict)
storage_options: Dict[str, Any] = Field(default_factory=dict)
placeholders: Optional[Dict[str, Any]] = None
rename: Dict[str, str] = Field(default_factory=dict)
nodata: Optional[Number] = None
meta: Optional[DataCatalogItemMetadata] = None
unit_add: Optional[Dict[str, Number]] = None
unit_mult: Optional[Dict[str, Number]] = None
variants: Optional[List[SourceVariant]] = None
version: Optional[Union[str, Number]] = None

model_config: ConfigDict = ConfigDict(
str_strip_whitespace=True,
extra="forbid",
)

@model_validator(mode="after")
def _check_valid_crs(self) -> "DataCatalogItem":
try:
if self.crs:
_ = CRS.from_user_input(self.crs)
except CRSError as e:
raise ValueError(e)
return self

@staticmethod
def from_dict(input_dict, name=None):
"""Convert a dictionary into a validated source item."""
Expand Down
24 changes: 21 additions & 3 deletions hydromt/validators/model_config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Pydantic models for the validation of model config files."""
from inspect import Parameter, signature
from typing import Any, Callable, Dict, Type
from typing import Any, Callable, Dict, List, Type

from pydantic import BaseModel, ConfigDict, Field

from hydromt.models import Model


class HydromtModelStep(BaseModel):
"""A Pydantic model for the validation of model config files."""
"""A Pydantic model for the validation of model setup functions."""

model: Type[Model]
fn: Callable
Expand All @@ -28,7 +28,9 @@ def from_dict(input_dict: Dict[str, Any], model: Model):
try:
fn = getattr(model, fn_name)
except AttributeError:
raise ValueError(f"Model does not have function {fn_name}")
raise ValueError(
f"Model of type {model.__name__} does not have function {fn_name}"
)

sig = signature(fn)
sig_has_var_keyword = (
Expand All @@ -54,3 +56,19 @@ def from_dict(input_dict: Dict[str, Any], model: Model):
)

return HydromtModelStep(model=model, fn=fn, args=arg_dict)


class HydromtModelSetup(BaseModel):
"""A Pydantic model for the validation of model setup files."""

steps: List[HydromtModelStep]

@staticmethod
def from_dict(input_dict: Dict[str, Any], model: Model):
"""Generate a validated model of a sequence steps in a model config file."""
return HydromtModelSetup(
steps=[
HydromtModelStep.from_dict({fn_name: fn_args}, model)
for fn_name, fn_args in input_dict.items()
]
)
Loading

0 comments on commit 0bf8e2a

Please sign in to comment.