diff --git a/docs/examples/twri.py b/docs/examples/twri.py index 9e36d7f..c2ac100 100644 --- a/docs/examples/twri.py +++ b/docs/examples/twri.py @@ -285,7 +285,8 @@ def plot_head(head, workspace): nc_fpth = workspace / "twri.input.nc" gwf.netcdf_file = nc_fpth -ds = gwf.to_xarray(format="structured") +nc_model = flopy4.mf6.netcdf.NetCDFModel.from_model(gwf) +ds = nc_model.to_xarray() ds.to_netcdf(nc_fpth) with flopy4.mf6.write_context.WriteContext(use_netcdf=True): @@ -303,7 +304,8 @@ def plot_head(head, workspace): nc_fpth = workspace / "twri.input.nc" gwf.netcdf_file = nc_fpth -ds = gwf.to_xarray(format="layered") +nc_model = flopy4.mf6.netcdf.NetCDFModel.from_model(gwf, mesh="layered") +ds = nc_model.to_xarray() ds.to_netcdf(nc_fpth) with flopy4.mf6.write_context.WriteContext(use_netcdf=True): diff --git a/flopy4/mf6/__init__.py b/flopy4/mf6/__init__.py index 5e835eb..a146e1a 100644 --- a/flopy4/mf6/__init__.py +++ b/flopy4/mf6/__init__.py @@ -12,11 +12,12 @@ from flopy4.mf6.component import Component from flopy4.mf6.converter import structure, unstructure from flopy4.mf6.ims import Ims +from flopy4.mf6.netcdf import NetCDFModel from flopy4.mf6.simulation import Simulation from flopy4.mf6.tdis import Tdis from flopy4.uio import DEFAULT_REGISTRY -__all__ = ["gwf", "simulation", "solution", "utils", "Ims", "Tdis", "Simulation"] +__all__ = ["gwf", "simulation", "solution", "utils", "Ims", "NetCDFModel", "Tdis", "Simulation"] class WriteError(Exception): diff --git a/flopy4/mf6/converter/egress/dataset.py b/flopy4/mf6/converter/egress/dataset.py deleted file mode 100644 index 9d05ec6..0000000 --- a/flopy4/mf6/converter/egress/dataset.py +++ /dev/null @@ -1,174 +0,0 @@ -import numpy as np -import xarray as xr -import xattree - -from flopy4.mf6.constants import FILL_DNODATA, FILL_FLOAT64, FILL_INT32, FILL_INT64 -from flopy4.mf6.model import Model -from flopy4.mf6.package import Package - - -# TODO: longname, auxiliary -def xarray_flat(dt: xr.DataTree, mesh_type: str | None = None): - mesh_type = mesh_type.lower() if mesh_type is not None else None - model = dt.attrs["host"] # type: ignore - assert isinstance(model, Model) - - mname = model.name # type: ignore - mtype = model.__class__.__name__.lower() - nlay = model.data.dims["nlay"] # type: ignore - - # set grid_type # TODO dis vs disv child? - if "nrow" in model.data.dims and "ncol" in model.data.dims: # type: ignore - grid_type = "structured" - elif "ncpl" in model.data.dims: # type: ignore - grid_type = "vertext" - else: - pass # raise? - - def _dims(shape, layer=False): - dimmap = { - "time": model.data.dims["nper"], - "z": model.data.dims["nlay"], - "y": model.data.dims["nrow"], - "nmesh_face": model.data.dims["ncpl"], - "x": model.data.dims["ncol"], - } - - shp = list(shape) - if layer: - idx = 1 if shape[0] == "time" else 0 - shp.insert(idx, "z") - return [dimmap[s] for s in shp] - - def _encode(varname, shape, dataset): - if dataset[varname].dtype == np.float64: - if "time" in shape: - dataset[varname].fillna(FILL_DNODATA) - else: - dataset[varname].fillna(FILL_FLOAT64) - elif dataset[varname].dtype == np.int64: - dataset[varname].fillna(FILL_INT64) - elif dataset[varname].dtype == np.int32: - dataset[varname].fillna(FILL_INT32) - return dataset - - def _add_gridvar(field_name, package, multi, data, dataset): - ptype = package.__class__.__name__.lower() - shape = ["time"] if "nper" in data[field_name].dims else [] - if "nodes" in data[field_name].dims: - shape += ["z", "y", "x"] - elif "ncpl" in data[field_name].dims: - shape += ["y", "x"] - else: - if "nlay" in data[field_name].dims: - shape.append("z") - if "nrow" in data[field_name].dims: - shape.append("y") - if "ncol" in data[field_name].dims: - shape.append("x") - if multi: - varname = f"{package.name}_{field_name}" - mf6_input = f"{mname}/{package.name}/{field_name}" - else: - varname = f"{ptype}_{field_name}" - mf6_input = f"{mname}/{ptype}/{field_name}" - - var_d = {varname: (shape, data[field_name].values.reshape(_dims(shape)))} - dataset = dataset.assign(var_d) - dataset[varname].attrs["modflow_input"] = mf6_input - dataset = _encode(varname, shape, dataset) - - return dataset - - def _add_layered_gridvars(field_name, package, multi, data, dataset): - ptype = package.__class__.__name__.lower() - shape = ["time"] if "nper" in data[field_name].dims else [] - if ( - "nodes" in data[field_name].dims - or "ncpl" in data[field_name].dims - or ("nrow" in data[field_name].dims and "ncol" in data[field_name].dims) - ): - shape.append("nmesh_face") - elif "nrow" in data[field_name].dims: - shape.append("y") - elif "ncol" in data[field_name].dims: - shape.append("x") - if multi: - mf6_input = f"{mname}/{package.name}/{field_name}" - basename = f"{package.name}_{field_name}" - else: - mf6_input = f"{mname}/{ptype}/{field_name}" - basename = f"{ptype}_{field_name}" - - if ( - data[field_name].dims == ("nper", "nodes") - or "nlay" in data[field_name].dims - or "nodes" in data[field_name].dims - ): - for layer in range(nlay): - varname = f"{basename}_l{layer + 1}" - if data[field_name].dims == ("nper", "nodes"): - var_d = { - varname: ( - shape, - data[field_name].values.reshape(_dims(shape, True))[:, layer, :], - ) - } - elif "nlay" in data[field_name].dims: - var_d = {varname: (shape, data[field_name].values[layer, :, :].ravel())} - elif "nodes" in data[field_name].dims: - var_d = { - varname: ( - shape, - data[field_name].values.reshape(_dims(shape, True))[layer, :].ravel(), - ) - } - dataset = dataset.assign(var_d) - dataset[varname].attrs["modflow_input"] = mf6_input - dataset[varname].attrs["layer"] = layer + 1 - dataset = _encode(varname, shape, dataset) - else: - varname = basename - var_d = {varname: (shape, data[field_name].values.reshape(_dims(shape)))} - dataset = dataset.assign(var_d) - dataset[varname].attrs["modflow_input"] = mf6_input - dataset = _encode(varname, shape, dataset) - return dataset - - ds = xr.Dataset() - ds.attrs["modflow_model"] = f"{mtype}6: {mname}" - ds.attrs["modflow_grid"] = f"{grid_type}" - if mesh_type is not None: - ds.attrs["mesh"] = mesh_type - - for c in model.children: # type: ignore - package = model.children[c] # type: ignore - assert isinstance(package, Package) - xatspec = xattree.get_xatspec(type(package)) - - if hasattr(package, "multi_package"): - multi = package.multi_package - else: - multi = False - - data = xattree.asdict(package) - - for block_name, block in package.dfn.blocks.items(): - if block_name != "griddata" and block_name != "period": - continue - for field_name in block.keys(): - if ( - data[field_name] is None - or field_name not in xatspec.arrays - or not hasattr(xatspec.arrays[field_name], "metadata") - or "netcdf" not in xatspec.arrays[field_name].metadata # type: ignore - or not xatspec.arrays[field_name].metadata["netcdf"] # type: ignore - ): - continue - - if mesh_type is None: - ds = _add_gridvar(field_name, package, multi, data, ds) - elif mesh_type == "layered": - ds = _add_layered_gridvars(field_name, package, multi, data, ds) - - return ds diff --git a/flopy4/mf6/model.py b/flopy4/mf6/model.py index 985370a..a3ebd97 100644 --- a/flopy4/mf6/model.py +++ b/flopy4/mf6/model.py @@ -1,6 +1,5 @@ from abc import ABC -import xarray as xr from xattree import xattree from flopy4.mf6.context import Context @@ -10,15 +9,3 @@ class Model(Context, ABC): def default_filename(self) -> str: return f"{self.name}.nam" # type: ignore - - def to_xarray(self, format: str | None = None) -> xr.Dataset: - from flopy4.mf6.converter.egress.dataset import xarray_flat - - dt = super().to_xarray() - if format is not None: - mesh_type = None - if format.lower() == "layered": - mesh_type = "layered" - return xarray_flat(dt, mesh_type) - else: - return dt diff --git a/flopy4/mf6/netcdf.py b/flopy4/mf6/netcdf.py new file mode 100644 index 0000000..59f2cd0 --- /dev/null +++ b/flopy4/mf6/netcdf.py @@ -0,0 +1,772 @@ +import abc + +import numpy as np +import xarray as xr +from pydantic import ( + BaseModel, + ConfigDict, + Field, + ValidationError, + ValidationInfo, + field_validator, +) +from xattree import XatSpec, asdict, get_xatspec + +from flopy4.mf6.constants import FILL_DNODATA, FILL_FLOAT64, FILL_INT64 +from flopy4.mf6.model import Model +from flopy4.mf6.package import Package +from flopy4.version import __version__ + + +def metadata(attribute, key: str): + if hasattr(attribute, "metadata"): + meta = getattr(attribute, "metadata") + if key in meta: + return meta[key] + return None + + +def _pkgclass(package_name: str) -> Package: + import flopy4 + + mtype, ptype = package_name.lower().split("-") + if not hasattr(flopy4.mf6, mtype): + raise AssertionError(f"not a valid mf6 component: {package_name}") + model = getattr(flopy4.mf6, mtype) + if not hasattr(model, ptype): + raise AssertionError(f"not a valid mf6 component: {package_name}") + module = getattr(model, ptype) + return getattr(module, ptype.capitalize()) + + +def multi_package(package_name: str) -> bool: + package = _pkgclass(package_name) + if hasattr(package, "multi_package"): + return getattr(package, "multi_package") + return False + + +def get_spec(package_name: str) -> XatSpec: + return get_xatspec(_pkgclass(package_name)) # type: ignore + + +def dimmap(gridtype: str, dims: list[int]) -> dict: + _map = { + "time": dims[0], + "z": dims[1], + } + if gridtype == "structured": + _map["y"] = dims[2] + _map["x"] = dims[3] + _map["nmesh_face"] = dims[2] * dims[3] + elif gridtype == "vertex": + _map["nmesh_face"] = dims[2] + return _map + + +def lower(d: dict) -> dict: + return {k.lower(): v.lower() if isinstance(v, str) else v for k, v in d.items()} + + +# Define an Abstract Base Class (interface) +class NetCDFInput(abc.ABC): + @classmethod + @abc.abstractmethod + def from_dict(cls, meta: dict, context: dict | None): + """create new instance, validate against schema.""" + pass + + @abc.abstractmethod + def to_xarray(self) -> xr.Dataset: + """create xarray dataset.""" + pass + + @property + @abc.abstractmethod + def meta(self) -> dict: + """get meta dictionary property.""" + pass + + @abc.abstractmethod + def jsonschema(self) -> dict: + """create xarray dataset.""" + pass + + +class NetCDFModel(BaseModel, NetCDFInput): + attrs: "NetCDFModelAttrs" + packages: list["NetCDFPackage"] = Field(default_factory=list) + + def model_post_init(self, __context) -> None: + self._context = __context + + @classmethod + def from_dict(cls, meta, context=None): + try: + if context: + context = lower(context) + _meta = NetCDFModel._backfill_meta(meta, context) + inst = cls.model_validate(_meta, context=context) + inst._context |= context if context is not None else inst._context + return inst + except ValidationError: + raise + + @classmethod + def from_model(cls, model: Model, mesh: str | None = None): + assert hasattr(model, "name") + assert hasattr(model, "data") + + modeltype = model.__class__.__name__.lower() + attrs = {"title": f"{model.name.upper()} model input"} + packages = [] + distype = None + + if mesh is not None and mesh.lower() != "structured": + attrs["mesh"] = mesh.lower() + + for c in model.children: # type: ignore + package = model.children[c] # type: ignore + packagetype = package.__class__.__name__.lower() + distype = packagetype if packagetype.startswith("dis") else distype + # TODO: auxiliary + p = { + "package_name": package.name, + "package_type": f"{modeltype}-{packagetype}", + "params": [], + } + xatspec = get_xatspec(type(package)) + multi = package.multi_package if hasattr(package, "multi_package") else False + data = asdict(package) + + for block_name, block in package.dfn.blocks.items(): + if block_name != "griddata" and block_name != "period": + continue + for field_name in block.keys(): + if ( + data[field_name] is None + or field_name not in xatspec.arrays + or not hasattr(xatspec.arrays[field_name], "metadata") + or "netcdf" not in xatspec.arrays[field_name].metadata # type: ignore + or not xatspec.arrays[field_name].metadata["netcdf"] # type: ignore + ): + continue + + p["params"].append({"name": field_name, "data": data[field_name].values}) + + if len(p["params"]) > 0: + packages.append(p) + + dims = [ + model.data.dims["nper"], + model.data.dims["nlay"], + ] + + if distype == "dis": + dims.append(model.data.dims["nrow"]) + dims.append(model.data.dims["ncol"]) + gridtype = "structured" + elif distype == "disv": + dims.append(model.data.dims["ncpl"]) + gridtype = "vertex" + + return NetCDFModel.from_dict( + meta={ + "modeltype": modeltype, + "modelname": model.name, + "gridtype": gridtype, + "attrs": attrs, + "packages": packages, + }, + context={"dims": dims}, + ) + + def to_xarray(self) -> xr.Dataset: + import datetime + + dss = [] + meta = self.model_dump(by_alias=True) + for p in self.packages: + dss.append(p.to_xarray()) + + ds = xr.merge(dss) + + dt = datetime.datetime.now() + timestamp = dt.strftime("%m/%d/%Y %H:%M:%S") + meta["attrs"]["source"] = f"pyphoenix {__version__}" + meta["attrs"]["history"] = f"first created {timestamp}" + # TODO: conventions, etc + + for a in meta["attrs"]: + if meta["attrs"][a] is not None: + ds.attrs[a] = meta["attrs"][a] + return ds + + @property + def meta(self): + """meta property getter.""" + return self.model_dump(by_alias=True) + + def jsonschema(self) -> dict: + return self.model_json_schema() + + @field_validator("attrs", mode="before") + @classmethod + def validate_attrs(cls, v: dict[str, str]) -> dict[str, str]: + """ + validate model (dataset) scoped attributes dictionary + """ + v = lower(v) + return v + + @staticmethod + def _backfill_meta(meta: dict, context: dict, verbose: bool = True) -> dict: + _meta = dict(meta) + + if "attrs" not in _meta: + _meta["attrs"] = {} + + _meta = lower(_meta) + _meta["attrs"]["modflow_grid"] = _meta["gridtype"].lower() + _meta["attrs"]["modflow_model"] = ( + f"{_meta['modeltype'].lower()}: {_meta['modelname'].lower()}" + ) + + _packages = [] + for pkg in _meta["packages"]: + pkgctx = {"mesh": _meta["attrs"]["mesh"]} if "mesh" in _meta["attrs"] else {} + pkgctx["modelname"] = _meta["modelname"] + pkgctx["gridtype"] = _meta["gridtype"] + pkgctx |= context + _packages.append(NetCDFPackage.from_dict(pkg, context=pkgctx)) + _meta["packages"] = _packages + + return _meta + + +class NetCDFModelAttrs(BaseModel): + # order of params dictates when data added to info dict + mesh: str | None = Field(default=None) + modflow_grid: str = Field() + modflow_model: str = Field() + + model_config = ConfigDict(extra="allow") + + @field_validator("mesh", mode="before") + @classmethod + def validate_mesh(cls, v: str | None, info: ValidationInfo) -> str | None: + """ + validate model mesh attribute + """ + if v is not None: + v = v.lower() + if v != "layered": + raise ValueError("only LAYERED mesh supported") + info.context["mesh"] = v # type: ignore + return v + + @field_validator("modflow_grid", mode="before") + @classmethod + def validate_modflow_grid(cls, v: str, info: ValidationInfo) -> str: + """ + validate model modflow_grid attribute + """ + v = v.lower() + dims = info.context.get("dims") # type: ignore + if v == "structured": + if len(dims) != 4: + raise AssertionError( + "expected 4 input dimensions [time, nlay, nrow, ncol]" + " for structured discretization: {dims}" + ) + elif v == "vertex": + if len(dims) != 3: + raise AssertionError( + "expected 3 input dimensions [time, nlay, ncpl]" + " for vertex discretization: {dims}" + ) + info.context["gridtype"] = v # type: ignore + return v + + @field_validator("modflow_model", mode="before") + @classmethod + def validate_modflow_model(cls, v: str, info: ValidationInfo) -> str: + """ + validate model modflow_model attribute + """ + v = v.lower() + tokens = v.split(":") + if len(tokens) != 2: + raise ValueError(f"invalid modflow_model attribute: {v}") + modeltype = tokens[0].strip() + if modeltype[-1].isdigit(): + modeltype = modeltype[:-1] + info.context["modeltype"] = modeltype # type: ignore + info.context["modelname"] = tokens[1].strip() # type: ignore + return v + + +class NetCDFPackage(BaseModel, NetCDFInput): + package_name: str = Field() + package_type: str = Field() + params: list["NetCDFParam"] = Field(default_factory=list) + auxiliary: list[str] = Field(default_factory=list) + + def model_post_init(self, __context) -> None: + self._context = __context + + if len(self.auxiliary) > 0: + for p in self.params: + if p.name == "aux": + p._context["auxiliary"] = self.auxiliary + + @classmethod + def from_dict(cls, meta, context): + try: + if context: + context = lower(context) + _meta = NetCDFPackage._backfill_meta(meta, context) + inst = cls.model_validate(_meta, context=context) + inst._context |= context if context is not None else inst._context + return inst + except ValidationError: + raise + + def to_xarray(self) -> xr.Dataset: + ds = [] + for p in self.params: + ds.append(p.to_xarray()) + + return xr.merge(ds) + + @property + def meta(self): + """meta property getter.""" + return self.model_dump(by_alias=True) + + def jsonschema(self) -> dict: + return self.model_json_schema() + + @field_validator("package_name", mode="before") + @classmethod + def validate_package_name(cls, v: str, info: ValidationInfo) -> str: + """ + validate package_name string + """ + v = v.lower() + info.context["package_name"] = v # type: ignore + return v + + @field_validator("package_type", mode="before") + @classmethod + def validate_package_type(cls, v: str, info: ValidationInfo) -> str: + """ + validate package_type string [-] + """ + v = v.lower() + assert get_spec(v) + info.context["package_type"] = v # type: ignore + return v + + @field_validator("auxiliary", mode="before") + @classmethod + def validate_auxiliary(cls, v: list[str]) -> list[str]: + """ + validate package auxiliary list + """ + v = [aux.lower() for aux in v] + return v + + @staticmethod + def _backfill_meta(meta: dict, context: dict, verbose: bool = True) -> dict: + _meta = dict(meta) + + if "package_name" not in _meta or "package_type" not in _meta: + raise AssertionError( + "package missing required package_name or package_type attribute(s)." + ) + + auxiliary = _meta.get("auxiliary", None) + + paramctx = dict(context) + paramctx["package_name"] = _meta["package_name"] + paramctx["package_type"] = _meta["package_type"] + if auxiliary is not None: + paramctx["auxiliary"] = auxiliary + + spec = get_spec(paramctx["package_type"].lower()) + + dims = context.get("dims", None) + mesh = context.get("mesh", None) + if dims is None: + raise AssertionError("dimensions are required context") + + def _add_layered_param(p): + for layer in range(dims[1]): + p["attrs"]["layer"] = layer + 1 + _params.append(NetCDFParam.from_dict(p, context=paramctx)) + + _params = [] + for p in _meta["params"]: + if "attrs" not in p: + p["attrs"] = {} + + if p["name"].lower() == "aux" and (auxiliary is None or len(auxiliary) == 0): + raise AssertionError("AUX parameter requires auxiliary list input.") + + shape = spec.arrays[p["name"]].dims + assert shape is not None + gridded = "nodes" in shape or "nlay" in shape + + if not gridded or mesh is None: + assert "layer" not in p["attrs"] + if p["name"].lower() == "aux": + for i, aux in enumerate(auxiliary): # type: ignore + p["attrs"]["modflow_iaux"] = i + 1 + _params.append(NetCDFParam.from_dict(p, context=paramctx)) + else: + _params.append(NetCDFParam.from_dict(p, context=paramctx)) + + else: + if p["name"].lower() == "aux": + for i, aux in enumerate(auxiliary): # type: ignore + p["attrs"]["modflow_iaux"] = i + 1 + _add_layered_param(p) + else: + _add_layered_param(p) + + _meta["params"] = _params + + return _meta + + +class NetCDFParam(BaseModel, NetCDFInput): + name: str = Field() + shape: list[str] = Field(default_factory=list) + attrs: "NetCDFParamAttrs" + encodings: "NetCDFParamEncodings" + dtype: np.dtype = Field() + data: np.ndarray | None = None + + # Allow Pydantic to handle non-native types (numpy) + model_config = ConfigDict(arbitrary_types_allowed=True) + + def model_post_init(self, __context) -> None: + self._context = __context + self._context["dimmap"] = dimmap(self._context["gridtype"], self._context["dims"]) + + @classmethod + def from_dict(cls, meta, context): + try: + if context: + context = lower(context) + # TODO: verify required context + if ( + "modelname" not in context + or "package_name" not in context + or "package_type" not in context + or "gridtype" not in context + or "dims" not in context + ): + raise AssertionError( + "NetCDFParam incomplete context: modelanme, package_name, " + "package_type, gridtype and dims are required." + ) + context["dimmap"] = dimmap(context["gridtype"], context["dims"]) + _meta = NetCDFParam._backfill_meta(meta, context) + inst = cls.model_validate(_meta, context=context) + inst._context |= context if context is not None else inst._context + return inst + except ValidationError: + raise + + def to_xarray(self) -> xr.Dataset: + meta = self.model_dump(by_alias=True) + package_name = self._context["package_name"] + package_type = self._context["package_type"] + mesh = self._context.get("mesh", None) + auxiliary = self._context.get("auxiliary", None) + ptype = package_type.split("-")[1].strip() + spec = get_spec(package_type) + + ds = xr.Dataset() + + basename = ( + ( + auxiliary[meta["attrs"]["modflow_iaux"] - 1] + if auxiliary is not None + else f"aux{meta['attrs']['modflow_iaux']}" + ) + if meta["name"] == "aux" + else meta["name"] + ) + param = ( + basename + if (mesh is None or "layer" not in meta["attrs"] or meta["attrs"]["layer"] is None) + else f"{basename}_l{meta['attrs']['layer']}" + ) + varname = f"{package_name}_{param}" if multi_package(package_type) else f"{ptype}_{param}" + + if "data" in meta and meta["data"] is not None: + data = meta["data"] + else: + dims = [self._context["dimmap"][dim] for dim in meta["shape"]] + data = np.full( + dims, + meta["encodings"]["_FillValue"], + dtype=meta["dtype"], + ) + + var_d = {varname: (meta["shape"], data)} + ds = ds.assign(var_d) + + for a in meta["attrs"]: + if meta["attrs"][a] is not None: + ds[varname].attrs[a] = meta["attrs"][a] + for e in meta["encodings"]: + if meta["encodings"][e] is not None: + ds[varname].encoding[e] = meta["encodings"][e] + + return ds + + @property + def meta(self): + """meta property getter.""" + return self.model_dump(by_alias=True) + + def jsonschema(self) -> dict: + return self.model_json_schema() + + @field_validator("name", mode="before") + @classmethod + def validate_name(cls, v: str, info: ValidationInfo) -> str: + """ + validate parameter name + """ + v = v.lower() + package = info.context.get("package_type") # type: ignore + spec = get_spec(package) + + if v not in spec.arrays: + raise ValueError(f"param {v} not found in package {package}") + if not metadata(spec.arrays[v], "netcdf"): + raise ValueError(f"not a netcdf param: '{v}'") + return v + + @field_validator("shape", mode="before") + @classmethod + def validate_shape(cls, v: list[str]) -> list[str]: + """ + validate parameter shape + """ + v = [dim.lower() if isinstance(dim, str) else dim for dim in v] + valid = ["time", "nmesh_face", "z", "y", "x"] + if not all(dim in valid for dim in v): + raise AssertionError(f"invalid param shape={v}. Valid dims={valid}.") + return v + + @field_validator("attrs", mode="before") + @classmethod + def validate_attrs(cls, v: dict[str, int | str], info: ValidationInfo) -> dict[str, int | str]: + """ + validate parameter attributes dictionary + """ + v = lower(v) + param = info.data.get("name") + mesh = info.context.get("mesh") # type: ignore + shape = info.data.get("shape") + assert shape + gridded = "nodes" in shape or "nlay" in shape + if gridded and mesh is not None and ("layer" not in v or v["layer"] is None): + raise AssertionError(f"expected layer attribute for mesh param '{param}'") + if param is not None and param == "aux" and "modflow_iaux" not in v: + raise AssertionError("expected modflow_iaux attribute for aux param") + return v + + @field_validator("encodings", mode="before") + @classmethod + def validate_encodings(cls, v: dict[str, int | float | str]) -> dict[str, int | float | str]: + """ + validate parameter encodings dictionary + """ + return v + + @field_validator("dtype", mode="before") + @classmethod + def validate_dtype(cls, v: np.dtype) -> np.dtype: + """ + validate parameter dtype + """ + if not (np.issubdtype(v, np.floating) or np.issubdtype(v, np.integer)): + raise AssertionError(f"invalid param dtype={v}, expected numpy numeric dtype.") + return v + + @field_validator("data", mode="before") + @classmethod + def validate_data(cls, v: np.ndarray) -> np.ndarray: + """ + validate parameter data + """ + return v + + @staticmethod + def _backfill_meta(meta: dict, context: dict, verbose: bool = True) -> dict: + _meta = dict(meta) + param = _meta["name"] + modelname = context["modelname"] + mesh = context.get("mesh", None) + spec = get_spec(context["package_type"]) + + if param not in spec.arrays: + raise ValueError(f"param {param} not found in package {context['package_type']}") + + if "attrs" not in _meta: + _meta["attrs"] = {} + if "encodings" not in _meta: + _meta["encodings"] = {} + + # add long_name to parameter attributes + _meta["attrs"]["long_name"] = metadata(spec.arrays[param], "longname") + + def _structured_shape(dfn_shape): + shape = ["time"] if "nper" in dfn_shape else [] + if "nodes" in dfn_shape: + shape += ["z", "y", "x"] + elif "ncpl" in dfn_shape: + shape += ["y", "x"] + else: + if "nlay" in dfn_shape: + shape.append("z") + if "nrow" in dfn_shape: + shape.append("y") + if "ncol" in dfn_shape: + shape.append("x") + return shape + + def _mesh_shape(dfn_shape): + shape = ["time"] if "nper" in dfn_shape else [] + if ( + "nodes" in dfn_shape + or "ncpl" in dfn_shape + or ("nrow" in dfn_shape and "ncol" in dfn_shape) + ): + shape.append("nmesh_face") + elif "nrow" in dfn_shape: + shape.append("y") + elif "ncol" in dfn_shape: + shape.append("x") + return shape + + # dtype + _meta["dtype"] = spec.arrays[param].dtype + if np.issubdtype(spec.arrays[param].dtype, np.floating): + _meta["encodings"]["_FillValue"] = ( + FILL_DNODATA if metadata(spec.arrays[param], "block") == "period" else FILL_FLOAT64 + ) + elif np.issubdtype(spec.arrays[param].dtype, np.integer): + _meta["encodings"]["_FillValue"] = ( + # FILL_DNODATA # TODO: FILL_INODATA + FILL_INT64 + ) + + # modflow_input internal attribute + ptype = context["package_type"].split("-")[1].strip() + _meta["attrs"]["modflow_input"] = ( + f"{modelname}/{context['package_name']}/{param}" + if multi_package(context["package_type"]) + else f"{modelname}/{ptype}/{param}" + ) + + # data dims + _meta["shape"] = ( + _mesh_shape(spec.arrays[param].dims) + if mesh is not None + else _structured_shape(spec.arrays[param].dims) + ) + + # optional data + if "data" in _meta: + dims = [context["dimmap"][dim] for dim in _meta["shape"]] + nval = np.prod(dims) + + if "layer" in _meta["attrs"]: + data = _meta["data"] + layer = _meta["attrs"]["layer"] - 1 + if data.size == nval * context["dimmap"]["z"]: + # provided data is for full grid + s = dims + if "nodes" in spec.arrays[param].dims: # type: ignore + if _meta["shape"][0] == "time": + s.insert(1, context["dimmap"]["z"]) + _meta["data"] = data.reshape(s)[:, layer, :] + else: + s.insert(0, context["dimmap"]["z"]) + _meta["data"] = data.reshape(s)[layer, :].ravel() + elif "nlay" in spec.arrays[param].dims: # type: ignore + s.insert(0, context["dimmap"]["z"]) + _meta["data"] = data.reshape(s)[layer, :].ravel() + else: + # assume provided data is correctly formatted + pass + + else: + data = _meta["data"] + assert data.size == nval + _meta["data"] = data.reshape(dims) + + return _meta + + +class NetCDFParamAttrs(BaseModel): + modflow_input: str = Field() + modflow_iaux: int | None = Field(default=None) + layer: int | None = Field(default=None) + + model_config = ConfigDict(extra="allow") + + @field_validator("modflow_input", mode="before") + @classmethod + def validate_modflow_input(cls, v: str, info: ValidationInfo) -> str: + """ + validate parameter modflow_input attribute + """ + v = v.lower() + modelname = info.context.get("modelname") # type: ignore + if v.split("/")[0] != modelname: + raise ValueError( + f'modflow_input attribute "{v}" does not match dataset modelname "{modelname}")' + ) + return v + + @field_validator("modflow_iaux", mode="before") + @classmethod + def validate_modflow_iaux(cls, v: int, info: ValidationInfo) -> int: + """ + validate parameter modflow_iaux attribute + """ + return v + + @field_validator("layer", mode="before") + @classmethod + def validate_layer(cls, v: int, info: ValidationInfo) -> int: + """ + validate parameter layer attribute + """ + dims = info.context.get("dims") # type: ignore + if v is not None and v > dims[1]: + raise ValueError(f"param layer attribute value {v} exceeds grid k") + return v + + +class NetCDFParamEncodings(BaseModel): + fill: float = Field(alias="_FillValue") + + model_config = ConfigDict(extra="allow") + + @field_validator("fill", mode="before") + @classmethod + def validate_fill(cls, v: float, info: ValidationInfo) -> float: + """ + validate parameter fill (_FillValue) encoding attribute + """ + return v diff --git a/pixi.lock b/pixi.lock index da08067..45a9563 100644 --- a/pixi.lock +++ b/pixi.lock @@ -48,6 +48,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/e7/b18bee0772d49c0f78d57f15a68e85257abf7224d9b910706abe8bd1dc0f/bokeh-3.8.1-py3-none-any.whl @@ -96,6 +97,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl @@ -117,6 +120,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl @@ -159,6 +163,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/e7/b18bee0772d49c0f78d57f15a68e85257abf7224d9b910706abe8bd1dc0f/bokeh-3.8.1-py3-none-any.whl @@ -207,6 +212,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/14/faf1b90d267cea68d7e70662e7f88cefdb1bc890bd596c74b959e0517a72/pyproj-3.7.2-cp313-cp313-macosx_13_0_x86_64.whl @@ -228,6 +235,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl @@ -271,6 +279,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/e7/b18bee0772d49c0f78d57f15a68e85257abf7224d9b910706abe8bd1dc0f/bokeh-3.8.1-py3-none-any.whl @@ -319,6 +328,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz @@ -340,6 +351,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl @@ -378,6 +390,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5d/e7/b18bee0772d49c0f78d57f15a68e85257abf7224d9b910706abe8bd1dc0f/bokeh-3.8.1-py3-none-any.whl @@ -427,6 +440,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/0f/747974129cf0d800906f81cd25efd098c96509026e454d4b66868779ab04/pyproj-3.7.2-cp313-cp313-win_amd64.whl @@ -448,6 +463,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl @@ -506,6 +522,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -645,6 +662,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/e5/53c0a1c428f0976bf22f513d79c73000926cb00b9c138d8e02daf2102e18/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -696,6 +715,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -744,6 +764,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -882,6 +903,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/26/5c/f259e2526c67eb4b9e511741b19870a02363a47a35edbebc55c3178db22d/pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -932,6 +955,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -981,6 +1005,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -1119,6 +1144,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2e/b7/18f611a8cdc43417f9394a3ccd3eace2f32183c08b9eddc3d17681819f37/pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -1169,6 +1196,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -1213,6 +1241,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -1349,6 +1378,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/f3/cec89bd99fa3abf826f14d4e53d3d11340ce6f6af4d14bdcd54cd83b6576/pyarrow-22.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -1401,6 +1432,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -1467,6 +1499,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -1585,6 +1618,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f5/e5/53c0a1c428f0976bf22f513d79c73000926cb00b9c138d8e02daf2102e18/pyarrow-22.0.0-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -1628,6 +1663,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -1675,6 +1711,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -1794,6 +1831,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/26/5c/f259e2526c67eb4b9e511741b19870a02363a47a35edbebc55c3178db22d/pyarrow-22.0.0-cp311-cp311-macosx_12_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -1837,6 +1876,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -1885,6 +1925,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -2004,6 +2045,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2e/b7/18f611a8cdc43417f9394a3ccd3eace2f32183c08b9eddc3d17681819f37/pyarrow-22.0.0-cp311-cp311-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -2047,6 +2090,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -2090,6 +2134,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -2207,6 +2252,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/f3/cec89bd99fa3abf826f14d4e53d3d11340ce6f6af4d14bdcd54cd83b6576/pyarrow-22.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -2251,6 +2298,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -2316,6 +2364,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -2432,6 +2481,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -2475,6 +2526,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -2521,6 +2573,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -2638,6 +2691,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -2681,6 +2736,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -2728,6 +2784,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -2845,6 +2902,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -2888,6 +2947,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -2930,6 +2990,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -3045,6 +3106,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -3089,6 +3152,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -3153,6 +3217,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl @@ -3270,6 +3335,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1b/8b/5362443737a5307a7b67c1017c42cd104213189b4970bf607e05faf9c525/pyarrow-22.0.0-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -3313,6 +3380,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -3361,6 +3429,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h8210216_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -3479,6 +3548,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/9c/1d6357347fbae062ad3f17082f9ebc29cc733321e892c0d2085f42a2212b/pyarrow-22.0.0-cp313-cp313-macosx_12_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -3522,6 +3593,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -3571,6 +3643,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl @@ -3689,6 +3762,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a6/d6/d0fac16a2963002fc22c8fa75180a838737203d558f0ed3b564c4a54eef5/pyarrow-22.0.0-cp313-cp313-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -3732,6 +3807,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -3776,6 +3852,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda + - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -3892,6 +3969,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/f8/1d0bd75bf9328a3b826e24a16e5517cd7f9fbf8d34a3184a4566ef5a7f29/pyarrow-22.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl @@ -3936,6 +4015,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl @@ -3974,6 +4054,13 @@ packages: purls: [] size: 23621 timestamp: 1650670423406 +- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + name: annotated-types + version: 0.7.0 + sha256: 1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 + requires_dist: + - typing-extensions>=4.0.0 ; python_full_version < '3.9' + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl name: anyio version: 4.11.0 @@ -5318,7 +5405,7 @@ packages: - pypi: ./ name: flopy4 version: 0.0.1.dev0 - sha256: e2bd4bc603e77ab202708920decb6d1ba9ac82b48b6f8f9229fed27374e1ed3f + sha256: eadc31c7327ee94947ccda7d487fefd855676fc33a50fcb090a6e05f10c9d378 requires_dist: - modflow-devtools[dfn] @ git+https://github.com/MODFLOW-ORG/modflow-devtools.git@dfn - xattree @ git+https://github.com/wpbonelli/xattree.git @@ -5339,6 +5426,7 @@ packages: - netcdf4>=1.7.3,<2 - pyshp>=3.0.2.post1,<4 - llvmlite>=0.45.1,<0.46 + - pydantic - flopy4[build,lint,test] ; extra == 'dev' - codespell[toml]>=2.2.2 ; extra == 'lint' - ruff ; extra == 'lint' @@ -11389,6 +11477,102 @@ packages: version: '2.23' sha256: e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934 requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl + name: pydantic + version: 2.12.5 + sha256: e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d + requires_dist: + - annotated-types>=0.6.0 + - pydantic-core==2.41.5 + - typing-extensions>=4.14.1 + - typing-inspection>=0.4.2 + - email-validator>=2.0.0 ; extra == 'email' + - tzdata ; python_full_version >= '3.9' and sys_platform == 'win32' and extra == 'timezone' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl + name: pydantic-core + version: 2.41.5 + sha256: 76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl + name: pydantic-core + version: 2.41.5 + sha256: 7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl + name: pydantic-core + version: 2.41.5 + sha256: 1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: 941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl + name: pydantic-core + version: 2.41.5 + sha256: 112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl + name: pydantic-core + version: 2.41.5 + sha256: 79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl + name: pydantic-core + version: 2.41.5 + sha256: 070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: 406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl + name: pydantic-core + version: 2.41.5 + sha256: a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6 + requires_dist: + - typing-extensions>=4.14.1 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/b8/20/e989ab34f456f2f86a8d1e9013d09cc022e7508868e452084f49210b912e/pydap-3.5.8-py3-none-any.whl name: pydap version: 3.5.8 @@ -13208,6 +13392,13 @@ packages: version: 4.15.0 sha256: f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl + name: typing-inspection + version: 0.4.2 + sha256: 4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 + requires_dist: + - typing-extensions>=4.12.0 + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl name: tzdata version: '2025.2' diff --git a/pyproject.toml b/pyproject.toml index c214ce0..0985e0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ dependencies = [ "netcdf4>=1.7.3,<2", "pyshp>=3.0.2.post1,<4", "llvmlite>=0.45.1,<0.46", + "pydantic", ] dynamic = ["version"] diff --git a/test/test_mf6_component.py b/test/test_mf6_component.py index 1b82b24..31c00e4 100644 --- a/test/test_mf6_component.py +++ b/test/test_mf6_component.py @@ -450,6 +450,8 @@ def test_quickstart_grid(function_tmpdir): def test_quickstart_netcdf(function_tmpdir): + from flopy4.mf6.netcdf import NetCDFModel + sim_name = "quickstart" gwf_name = "mymodel" @@ -498,7 +500,8 @@ def test_quickstart_netcdf(function_tmpdir): nc_fpth = function_tmpdir / f"{gwf_name}.input.nc" gwf.netcdf_file = nc_fpth - ds = gwf.to_xarray(format="structured") + nc_model = NetCDFModel.from_model(gwf) + ds = nc_model.to_xarray() ds.to_netcdf(nc_fpth) with WriteContext(use_netcdf=True): @@ -526,7 +529,7 @@ def test_quickstart_netcdf(function_tmpdir): lines = fh.readlines() assert " HEAD NETCDF\n" in lines - ds = xr.load_dataset(nc_fpth) + ds = xr.load_dataset(nc_fpth, mask_and_scale=False) assert ("dis_delr") in ds assert ("dis_delc") in ds assert ("dis_top") in ds @@ -552,6 +555,8 @@ def test_quickstart_netcdf(function_tmpdir): def test_quickstart_netcdf_mesh(function_tmpdir): + from flopy4.mf6.netcdf import NetCDFModel + sim_name = "quickstart" gwf_name = "mymodel" @@ -600,7 +605,8 @@ def test_quickstart_netcdf_mesh(function_tmpdir): nc_fpth = function_tmpdir / f"{gwf_name}.input.nc" gwf.netcdf_file = nc_fpth - ds = gwf.to_xarray(format="layered") + nc_model = NetCDFModel.from_model(gwf, mesh="layered") + ds = nc_model.to_xarray() ds.to_netcdf(nc_fpth) with WriteContext(use_netcdf=True): @@ -628,7 +634,7 @@ def test_quickstart_netcdf_mesh(function_tmpdir): lines = fh.readlines() assert " HEAD NETCDF\n" in lines - ds = xr.load_dataset(nc_fpth) + ds = xr.load_dataset(nc_fpth, mask_and_scale=False) assert ("dis_delr") in ds assert ("dis_delc") in ds assert ("dis_top") in ds diff --git a/test/test_mf6_netcdf.py b/test/test_mf6_netcdf.py new file mode 100644 index 0000000..51fdfbb --- /dev/null +++ b/test/test_mf6_netcdf.py @@ -0,0 +1,374 @@ +import numpy as np +import xarray as xr + +from flopy4.mf6.netcdf import ( + FILL_DNODATA, + FILL_FLOAT64, + FILL_INT64, + NetCDFModel, + NetCDFPackage, + NetCDFParam, +) + + +def grid_nodata(dims): + grid = np.full((dims[1], dims[2], dims[3]), FILL_DNODATA, dtype=float) + return np.repeat(np.expand_dims(grid, axis=0), repeats=dims[0], axis=0) + + +def layer_nodata(dims): + layer = np.full((dims[2], dims[3]), FILL_DNODATA, dtype=float) + return np.repeat(np.expand_dims(layer, axis=0), repeats=dims[0], axis=0) + + +def test_model_nomesh(): + dims = [2, 4, 3, 2] # [nper, nlay, nrow, ncol] + welg_0_q = grid_nodata(dims) + welg_0_q[0, ...] = np.linspace(0, 1, 24).reshape([4, 3, 2]) + rcha_0_recharge = layer_nodata(dims) + rcha_0_recharge[1, ...] = np.linspace(1, 2, 6).reshape([3, 2]) + + packages = [ + { + "package_name": "welg_0", + "package_type": "gwf-wElg", + "auxiliary": ["concentration"], + "params": [ + {"name": "aux"}, + {"name": "q", "data": welg_0_q}, + ], + }, + { + "package_name": "rcha_0", + "package_type": "gwf-rcha", + "params": [ + {"name": "recharge", "data": rcha_0_recharge}, + ], + }, + ] + nc_cfg = { + "modeltype": "gwf6", + "modelname": "gwFmodel", + "gridtype": "structured", + "packages": packages, + } + + # classmethod to generate and validate model + nc_model = NetCDFModel.from_dict(nc_cfg, context={"dims": dims}) + meta = nc_model.meta + assert isinstance(meta, dict) + + # dataset from model instance + ds = nc_model.to_xarray() + assert isinstance(ds, xr.Dataset) + + assert ds.attrs["modflow_grid"] == "structured" + assert ds.attrs["modflow_model"] == "gwf6: gwfmodel" + assert "mesh" not in ds.attrs + assert "welg_0_q" in ds + assert "welg_0_concentration" in ds + assert np.allclose(ds["welg_0_q"].values, welg_0_q) + assert np.allclose(ds["welg_0_concentration"].values, FILL_DNODATA) + assert np.allclose(ds["rcha_0_recharge"].values, rcha_0_recharge) + assert ds["welg_0_q"].dims == ("time", "z", "y", "x") + assert ds["welg_0_concentration"].dims == ("time", "z", "y", "x") + assert ds["rcha_0_recharge"].dims == ("time", "y", "x") + assert ds.sizes["time"] == 2 + assert ds.sizes["z"] == 4 + assert ds.sizes["y"] == 3 + assert ds.sizes["x"] == 2 + assert len(ds) == 3 + + context = { + "modelname": "gwfmodel", + "gridtype": "structured", + "dims": [2, 4, 3, 2], + } + + for p in packages: + nc_pkg = NetCDFPackage.from_dict( + p, + context=context, + ) + ds = nc_pkg.to_xarray() + assert isinstance(ds, xr.Dataset) + meta = nc_pkg.meta + assert isinstance(meta, dict) + nc_pkg = NetCDFPackage.model_validate(meta, context=context) + + +def test_package_nomesh(): + packages = [ + { + "package_name": "welg_0", + "package_type": "gwf-welg", + "auxiliary": ["concentration", "temperature"], + "params": [ + {"name": "aux"}, + {"name": "q"}, + ], + }, + ] + + context = {"modelname": "gwfmodel", "gridtype": "structured", "dims": [1, 1, 1, 1]} + + nc_pkg = NetCDFPackage.from_dict(packages[0], context=context) + meta = nc_pkg.meta + assert isinstance(meta, dict) + nc_pkg = NetCDFPackage.model_validate(meta, context=context) + ds = nc_pkg.to_xarray() + assert isinstance(ds, xr.Dataset) + + # assert ds.attrs["modflow_grid"] == "structured" + # assert ds.attrs["modflow_model"] == "gwf6: gwfmodel" + assert "mesh" not in ds.attrs + assert "welg_0_q" in ds + assert "welg_0_concentration" in ds + assert "welg_0_temperature" in ds + assert np.allclose(ds["welg_0_q"].values.ravel(), FILL_DNODATA) + assert np.allclose(ds["welg_0_concentration"].values, FILL_DNODATA) + assert np.allclose(ds["welg_0_temperature"].values, FILL_DNODATA) + assert ds["welg_0_q"].dims == ("time", "z", "y", "x") + assert ds["welg_0_concentration"].dims == ("time", "z", "y", "x") + assert ds["welg_0_temperature"].dims == ("time", "z", "y", "x") + assert ds.sizes["time"] == 1 + assert ds.sizes["z"] == 1 + assert ds.sizes["y"] == 1 + assert ds.sizes["x"] == 1 + assert len(ds) == 3 + + +def test_param_nomesh(): + params = [ + {"name": "aux", "attrs": {"modflow_iaux": 1}}, + {"name": "q"}, + ] + context = { + "modelname": "gwfmodel", + "gridtype": "structured", + "package_name": "welg0", + "package_type": "gwf-welg", + "dims": [1, 1, 1, 1], + } + + for p in params: + nc_param = NetCDFParam.from_dict(p, context=context) + ds = nc_param.to_xarray() + assert isinstance(ds, xr.Dataset) + meta = nc_param.meta + assert isinstance(meta, dict) + nc_param = NetCDFParam.model_validate(meta, context=context) + + +def test_model_mesh(): + dims = [2, 4, 3, 2] # [nper, nlay, nrow, ncol] + dis_botm = np.linspace(11, 21, 24) + npf_k = np.linspace(0, 10, 24) + welg_0_q = grid_nodata(dims) + welg_0_q[0, ...] = np.linspace(0, 1, 24).reshape([4, 3, 2]) + rcha_0_recharge = np.linspace(1, 2, 12) + packages = [ + { + "package_name": "dis", + "package_type": "gwf-dis", + "params": [ + {"name": "delr"}, + {"name": "delc"}, + {"name": "idomain"}, + {"name": "botm", "data": dis_botm}, + ], + }, + { + "package_name": "npf", + "package_type": "gwf-npf", + "params": [ + {"name": "icelltype"}, + {"name": "k", "data": npf_k}, + {"name": "k22"}, + ], + }, + { + "package_name": "welg_0", + "package_type": "gwf-welg", + "auxiliary": ["concentration"], + "params": [ + {"name": "aux"}, + {"name": "q", "data": welg_0_q}, + ], + }, + { + "package_name": "rcha_0", + "package_type": "gwf-rcha", + "params": [ + {"name": "recharge", "data": rcha_0_recharge}, + ], + }, + ] + nc_cfg = { + "modeltype": "gwf6", + "modelname": "gwfmodel", + "gridtype": "structured", + "attrs": {"mesh": "layered"}, + "packages": packages, + } + + # classmethod to generate and validate model + nc_model = NetCDFModel.from_dict(nc_cfg, context={"dims": dims}) + + # dataset from model instance + ds = nc_model.to_xarray() + + assert ds.attrs["modflow_grid"] == "structured" + assert ds.attrs["modflow_model"] == "gwf6: gwfmodel" + assert ds.attrs["mesh"] == "layered" + assert "dis_delr" in ds + assert "dis_delc" in ds + assert "rcha_0_recharge" in ds + assert np.allclose(ds["dis_delr"].values, FILL_FLOAT64) + assert np.allclose(ds["dis_delc"].values, FILL_FLOAT64) + assert np.allclose(ds["rcha_0_recharge"].values.ravel(), rcha_0_recharge) + assert ds["dis_delr"].dims == ("x",) + assert ds["dis_delc"].dims == ("y",) + assert ds["rcha_0_recharge"].dims == ("time", "nmesh_face") + for layer in range(4): + assert f"dis_idomain_l{layer + 1}" in ds + assert f"dis_botm_l{layer + 1}" in ds + assert f"npf_k_l{layer + 1}" in ds + assert f"npf_k22_l{layer + 1}" in ds + assert f"npf_icelltype_l{layer + 1}" in ds + assert f"welg_0_q_l{layer + 1}" in ds + assert f"welg_0_concentration_l{layer + 1}" in ds + assert np.allclose(ds[f"dis_idomain_l{layer + 1}"].values, FILL_INT64) + assert np.allclose(ds[f"dis_botm_l{layer + 1}"].values, dis_botm.reshape([4, 6])[layer, :]) + assert np.allclose(ds[f"npf_k_l{layer + 1}"].values, npf_k.reshape([4, 6])[layer, :]) + assert np.allclose(ds[f"npf_k22_l{layer + 1}"].values, FILL_FLOAT64) + assert np.allclose(ds[f"npf_icelltype_l{layer + 1}"].values, FILL_INT64) + assert np.allclose( + ds[f"welg_0_q_l{layer + 1}"].values, + welg_0_q.reshape([2, 4, 6])[:, layer, :], + ) + assert np.allclose(ds[f"welg_0_concentration_l{layer + 1}"].values, FILL_DNODATA) + assert ds[f"dis_idomain_l{layer + 1}"].dims == ("nmesh_face",) + assert ds[f"npf_k_l{layer + 1}"].dims == ("nmesh_face",) + assert ds[f"npf_k22_l{layer + 1}"].dims == ("nmesh_face",) + assert ds[f"npf_icelltype_l{layer + 1}"].dims == ("nmesh_face",) + assert ds[f"welg_0_q_l{layer + 1}"].dims == ("time", "nmesh_face") + assert ds[f"welg_0_concentration_l{layer + 1}"].dims == ("time", "nmesh_face") + assert ds.sizes["time"] == 2 + assert ds.sizes["nmesh_face"] == 6 + assert ds.sizes["x"] == 2 + assert ds.sizes["y"] == 3 + assert len(ds) == 31 + + +def test_package_mesh(): + packages = [ + { + "package_name": "npf", + "package_type": "gwf-npf", + "params": [ + {"name": "icelltype"}, + {"name": "k"}, + {"name": "k22"}, + ], + }, + { + "package_name": "welg_0", + "package_type": "gwf-welg", + "auxiliary": ["concentration"], + "params": [ + {"name": "aux"}, + {"name": "q"}, + ], + }, + { + "package_name": "rcha0", + "package_type": "gwf-rcha", + "params": [ + {"name": "recharge"}, + {"name": "irch"}, + ], + }, + ] + + context = { + "mesh": "layered", + "modelname": "gwfmodel", + "gridtype": "structured", + "dims": [2, 3, 2, 2], + } + for p in packages: + nc_pkg = NetCDFPackage.from_dict(p, context=context) + ds = nc_pkg.to_xarray() + assert isinstance(ds, xr.Dataset) + meta = nc_pkg.meta + assert isinstance(meta, dict) + nc_pkg = NetCDFPackage.model_validate(meta, context=context) + + if p["package_type"] == "gwf-npf": + # TODO: still write model attrs? + assert len(ds.attrs) == 0 + for layer in range(3): + assert f"npf_k_l{layer + 1}" in ds + assert f"npf_k22_l{layer + 1}" in ds + assert f"npf_icelltype_l{layer + 1}" in ds + assert np.allclose(ds[f"npf_k_l{layer + 1}"].values, FILL_FLOAT64) + assert np.allclose(ds[f"npf_k22_l{layer + 1}"].values, FILL_FLOAT64) + assert np.allclose(ds[f"npf_icelltype_l{layer + 1}"].values, FILL_INT64) + assert ds[f"npf_k_l{layer + 1}"].dims == ("nmesh_face",) + assert ds[f"npf_k22_l{layer + 1}"].dims == ("nmesh_face",) + assert ds[f"npf_icelltype_l{layer + 1}"].dims == ("nmesh_face",) + assert ds.sizes["nmesh_face"] == 4 + assert len(ds) == 9 + + elif p["package_type"] == "gwf-welg": + # TODO: still write model attrs? + assert len(ds.attrs) == 0 + for layer in range(3): + assert f"welg_0_q_l{layer + 1}" in ds + assert f"welg_0_concentration_l{layer + 1}" in ds + assert np.allclose(ds[f"welg_0_q_l{layer + 1}"].values, FILL_DNODATA) + assert np.allclose(ds[f"welg_0_concentration_l{layer + 1}"].values, FILL_DNODATA) + assert ds[f"welg_0_q_l{layer + 1}"].dims == ("time", "nmesh_face") + assert ds[f"welg_0_concentration_l{layer + 1}"].dims == ("time", "nmesh_face") + assert ds.sizes["time"] == 2 + assert ds.sizes["nmesh_face"] == 4 + assert len(ds) == 6 + + elif p["package_type"] == "gwf-rcha": + # TODO: still write model attrs? + assert len(ds.attrs) == 0 + assert "rcha0_recharge" in ds + assert "rcha0_irch" in ds + assert np.allclose(ds["rcha0_recharge"].values, FILL_DNODATA) + assert np.allclose(ds["rcha0_irch"].values, FILL_INT64) + assert ds["rcha0_recharge"].dims == ("time", "nmesh_face") + assert ds["rcha0_irch"].dims == ("time", "nmesh_face") + assert ds.sizes["time"] == 2 + assert ds.sizes["nmesh_face"] == 4 + assert len(ds) == 2 + + +def test_param_mesh(): + params = [ + {"name": "aux", "attrs": {"layer": 1, "modflow_iaux": 1}}, + {"name": "aux", "attrs": {"layer": 2, "modflow_iaux": 1}}, + {"name": "q", "attrs": {"layer": 1}}, + {"name": "q", "attrs": {"layer": 2}}, + ] + context = { + "mesh": "layered", + "modelname": "gwfmodel", + "gridtype": "structured", + "package_name": "welg0", + "package_type": "gwf-welg", + "dims": [1, 2, 1, 1], + } + + for p in params: + nc_param = NetCDFParam.from_dict(p, context=context) + ds = nc_param.to_xarray() + assert isinstance(ds, xr.Dataset) + meta = nc_param.meta + assert isinstance(meta, dict) + nc_param = NetCDFParam.model_validate(meta, context=context)