diff --git a/flopy4/mf6/_mixin_fields.py b/flopy4/mf6/_mixin_fields.py index 3a8d328e..ca387545 100644 --- a/flopy4/mf6/_mixin_fields.py +++ b/flopy4/mf6/_mixin_fields.py @@ -225,27 +225,38 @@ def _field_types(cls: type) -> dict[str, Any]: return types +def _leaves(cls: type) -> list[type]: + """A class's subclasses that have none of their own, or the class if + it has none.""" + subs = cls.__subclasses__() + return [leaf for s in subs for leaf in _leaves(s)] if subs else [cls] + + def _missing(cls: type, chain: list[str]) -> list[str]: """Where a chain read from ``cls`` hits an attribute that isn't there, as ``Class.attr``. Past a field that can hold something else too (see - `_open`), an attribute only some of the classes have isn't missing.""" + `_open`), an attribute only some of the classes have isn't missing. + An attribute a base class lacks is looked for on its leaf subclasses, + which the DFNs may give it (e.g. ``Solution.models``).""" missing = [] at = {cls} unchecked = False for attr in chain: following: set[type] = set() following_unchecked = False - for c in at: - fields = _field_types(c) if attrs.has(c) else {} - if attr in fields: - following |= _classes(fields[attr]) - following_unchecked |= _open(fields[attr]) - elif hasattr(c, attr): - value = getattr(c, attr) - if isinstance(value, type) and attrs.has(value): - following.add(value) - elif not unchecked: - missing.append(f"{c.__qualname__}.{attr}") + for base in at: + on_base = hasattr(base, attr) or (attrs.has(base) and attr in _field_types(base)) + for c in [base] if on_base else _leaves(base): + fields = _field_types(c) if attrs.has(c) else {} + if attr in fields: + following |= _classes(fields[attr]) + following_unchecked |= _open(fields[attr]) + elif hasattr(c, attr): + value = getattr(c, attr) + if isinstance(value, type) and attrs.has(value): + following.add(value) + elif not unchecked: + missing.append(f"{c.__qualname__}.{attr}") at, unchecked = following, following_unchecked return missing diff --git a/flopy4/mf6/component.py b/flopy4/mf6/component.py index f4eb3e5c..412c0154 100644 --- a/flopy4/mf6/component.py +++ b/flopy4/mf6/component.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Optional import attrs -from attrs import fields +from attrs import Attribute, fields from flopy4.dimensions import DimensionResolverMixin from flopy4.mf6.constants import MF6 @@ -575,17 +575,22 @@ def _retarget_references(self) -> None: if any(a is not b for a, b in zip(referenced(new), referenced(value))): setattr(self, f.name, new) + def _reference_problem(self, field: Attribute, model: "Component") -> str | None: + """What's wrong with reference field `field` referring to `model` + (one of the simulation's models), or None. Subclasses say what they + accept (e.g. the model types an exchange connects).""" + return None + def _reference_errors(self) -> list[str]: """What's wrong with this component's references: a required one - missing, one not among the simulation's models, or one its field's - check finds a problem with (see `flopy4.spec.reference`).""" + missing, one not among the simulation's models, or one + `_reference_problem` finds a problem with.""" errors = [] models = getattr(self._parent, "models", []) for f in self._reference_fields(): value = self._resolved(f.name) if value is None and f.metadata["required"]: errors.append(f"{self.name} has no {f.name}") # type: ignore[attr-defined] - check = f.metadata["check"] for v in value if isinstance(value, list) else [value]: if v is None: continue @@ -594,7 +599,7 @@ def _reference_errors(self) -> list[str]: f"{self.name} refers to model {reference_names(v)!r} in {f.name}, " # type: ignore[attr-defined] "which isn't in the simulation" ) - elif check is not None and (problem := check(self, v)): + elif problem := self._reference_problem(f, v): errors.append(f"{self.name} {problem}") # type: ignore[attr-defined] return errors diff --git a/flopy4/mf6/converter/binding.py b/flopy4/mf6/converter/binding.py index f8e24a7a..0039fe11 100644 --- a/flopy4/mf6/converter/binding.py +++ b/flopy4/mf6/converter/binding.py @@ -20,6 +20,11 @@ def component_ftype(cls: type) -> str: return f"{cls_name.upper()}6" +def _flat(values) -> list: + """Reference fields' values as a row's terms, lists spread out.""" + return [v for value in values for v in (value if isinstance(value, list) else [value])] + + @define class Binding: """A serializable representation of a component.""" @@ -37,11 +42,13 @@ def to_tuple(self) -> tuple[str, ...]: @classmethod def from_component(cls, component: Component) -> "Binding": def _get_binding_terms(component: Component) -> tuple[str, ...] | None: - if isinstance(component, Exchange): - return tuple(reference_names([component.exgmnamea, component.exgmnameb])) - elif isinstance(component, Solution): + if isinstance(component, Solution): + # a lone solution listing none solves them all return tuple(reference_names(component._solved_models())) - elif isinstance(component, (Model, Package)): + if refs := component._reference_fields(): + # the reference fields are the row's terms, in order + return tuple(_flat(reference_names(getattr(component, f.name)) for f in refs)) + if isinstance(component, (Model, Package)): return (component.name,) # type: ignore return None diff --git a/flopy4/mf6/converter/ingress/structure.py b/flopy4/mf6/converter/ingress/structure.py index ad375ae3..aad81228 100644 --- a/flopy4/mf6/converter/ingress/structure.py +++ b/flopy4/mf6/converter/ingress/structure.py @@ -568,19 +568,19 @@ def _parse_readarray_period_block( def _apply_binding_terms(child: Any, terms: list, models: list) -> None: - from flopy4.mf6.exchange import Exchange - from flopy4.mf6.solution import Solution - + """Set the child's reference fields from the rest of its row, in + order: a single one takes a term, a list the rest.""" # the models loaded so far, so a loaded exchange or solution holds # them as a built one does; unknown names stay found = resolve([str(t) for t in terms], models) - if isinstance(child, Exchange): - if len(found) > 0: - child.exgmnamea = found[0] - if len(found) > 1: - child.exgmnameb = found[1] - elif isinstance(child, Solution) and found: - child.models = found + for f in child._reference_fields(): + if not found: + break + if f.metadata["many"]: + value, found = found, [] + else: + value, found = found[0], found[1:] + setattr(child, f.name, value) def _disambiguate_ga_variant(candidates: "list[type[Component]]", path: Path) -> "type[Component]": diff --git a/flopy4/mf6/ems.py b/flopy4/mf6/ems.py index ce44218b..2f4c757e 100644 --- a/flopy4/mf6/ems.py +++ b/flopy4/mf6/ems.py @@ -3,7 +3,9 @@ import attrs +from flopy4.mf6.model import Model from flopy4.mf6.solution import Solution +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) @@ -11,3 +13,5 @@ class Ems(Solution): dfn_name: ClassVar[str] = "sln-ems" slntype: ClassVar[str] = "ems" + + models: list[Model | str] = reference() diff --git a/flopy4/mf6/exchange.py b/flopy4/mf6/exchange.py index 1a722593..ac5a40d0 100644 --- a/flopy4/mf6/exchange.py +++ b/flopy4/mf6/exchange.py @@ -3,39 +3,26 @@ from typing import TYPE_CHECKING, Optional import attrs +from attrs import Attribute from flopy4.mf6.package import Package -from flopy4.spec import reference if TYPE_CHECKING: from flopy4.mf6.model import Model -def _connects(side: int): - """A check that a model on `side` (0 for a, 1 for b) is of the type the - exchange connects there (see `Exchange.model_types`).""" - - def check(exg: "Exchange", model: "Model") -> "str | None": - mtype = exg.model_types[side] - if type(model).dfn_name.partition("-")[0] == mtype: # type: ignore[attr-defined] - return None - return ( - f"({type(exg).__name__}) needs a {mtype.upper()} model {'ab'[side]}, " - f"not {model.name!r} ({type(model).__name__})" # type: ignore[attr-defined] - ) - - return check - - @attrs.define(kw_only=True, slots=False) class Exchange(Package, ABC): + """An exchange. Its `exgmnamea` and `exgmnameb` (generated, from its + row in the simulation's name file) are the models it connects, as + objects or names, of the types in `model_types`. The simulation owns + its models; an exchange only refers to them.""" + exgtype: Optional[type] = attrs.field(default=None) # type: ignore exgfile: Optional[Path] = attrs.field(default=None) # type: ignore - exgmnamea: "Model | str | None" = reference(many=False, check=_connects(0), required=True) - """The first model the exchange connects, as an object or a name. The - simulation owns its models; an exchange only refers to them.""" - exgmnameb: "Model | str | None" = reference(many=False, check=_connects(1), required=True) - """The second model the exchange connects, as an object or a name.""" + if TYPE_CHECKING: + exgmnamea: "Model | str | None" + exgmnameb: "Model | str | None" @property def model_types(self) -> tuple[str, str]: @@ -44,3 +31,15 @@ def model_types(self) -> tuple[str, str]: letters.)""" pair = type(self).dfn_name.partition("-")[2] # type: ignore[attr-defined] return pair[:3], pair[3:] + + def _reference_problem(self, field: Attribute, model) -> "str | None": + """A problem if the model isn't of the type the exchange connects on + the field's side (its references are a's, then b's, in row order).""" + side = [f.name for f in self._reference_fields()].index(field.name) + mtype = self.model_types[side] + if type(model).dfn_name.partition("-")[0] == mtype: + return None + return ( + f"({type(self).__name__}) needs a {mtype.upper()} model {'ab'[side]}, " + f"not {model.name!r} ({type(model).__name__})" + ) diff --git a/flopy4/mf6/exg/chfgwf.py b/flopy4/mf6/exg/chfgwf.py index e001e6a6..8beac65c 100644 --- a/flopy4/mf6/exg/chfgwf.py +++ b/flopy4/mf6/exg/chfgwf.py @@ -5,8 +5,10 @@ from flopy4.mf6.exchange import Exchange from flopy4.mf6.item import Item +from flopy4.mf6.model import Model from flopy4.mf6.spec import cellid_, child, double, integer, keyword, list_ from flopy4.mf6.utl.obs import Obs +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) @@ -22,6 +24,8 @@ class Exchangedata(Item): bedleak: float = double() cfact: float = double() + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) print_input: bool = keyword( default=False, block="options", diff --git a/flopy4/mf6/exg/gwegwe.py b/flopy4/mf6/exg/gwegwe.py index 7e215034..72ab9b7a 100644 --- a/flopy4/mf6/exg/gwegwe.py +++ b/flopy4/mf6/exg/gwegwe.py @@ -9,8 +9,10 @@ from flopy4.mf6.exchange import Exchange from flopy4.mf6.gwe.mve import Mve from flopy4.mf6.item import Item +from flopy4.mf6.model import Model from flopy4.mf6.spec import array, cellid_, child, double, integer, keyword, list_, string from flopy4.mf6.utl.obs import Obs +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) @@ -36,6 +38,8 @@ class Exchangedata(Item): default=None, empty_is_none=True, optional=True, when="boundnames" ) + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) gwfmodelname1: str = string( default=None, block="options", diff --git a/flopy4/mf6/exg/gwfgwe.py b/flopy4/mf6/exg/gwfgwe.py index ff270753..9075632a 100644 --- a/flopy4/mf6/exg/gwfgwe.py +++ b/flopy4/mf6/exg/gwfgwe.py @@ -4,8 +4,13 @@ import attrs from flopy4.mf6.exchange import Exchange +from flopy4.mf6.model import Model +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) class Gwfgwe(Exchange): dfn_name: ClassVar[str] = "exg-gwfgwe" + + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) diff --git a/flopy4/mf6/exg/gwfgwf.py b/flopy4/mf6/exg/gwfgwf.py index a73f3585..49592904 100644 --- a/flopy4/mf6/exg/gwfgwf.py +++ b/flopy4/mf6/exg/gwfgwf.py @@ -10,9 +10,11 @@ from flopy4.mf6.gwf.gnc import Gnc from flopy4.mf6.gwf.mvr import Mvr from flopy4.mf6.item import Item +from flopy4.mf6.model import Model from flopy4.mf6.record import Record from flopy4.mf6.spec import array, cellid_, child, double, integer, keyword, list_, record, string from flopy4.mf6.utl.obs import Obs +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) @@ -43,6 +45,8 @@ class Exchangedata(Item): default=None, empty_is_none=True, optional=True, when="boundnames" ) + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) auxiliary: Optional[NDArray[np.str_]] = array( default=None, converter=attrs.converters.optional(to_array(np.str_)), diff --git a/flopy4/mf6/exg/gwfgwt.py b/flopy4/mf6/exg/gwfgwt.py index d6ea4485..84723cca 100644 --- a/flopy4/mf6/exg/gwfgwt.py +++ b/flopy4/mf6/exg/gwfgwt.py @@ -4,8 +4,13 @@ import attrs from flopy4.mf6.exchange import Exchange +from flopy4.mf6.model import Model +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) class Gwfgwt(Exchange): dfn_name: ClassVar[str] = "exg-gwfgwt" + + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) diff --git a/flopy4/mf6/exg/gwfprt.py b/flopy4/mf6/exg/gwfprt.py index c3cf13c1..3e773ab3 100644 --- a/flopy4/mf6/exg/gwfprt.py +++ b/flopy4/mf6/exg/gwfprt.py @@ -4,8 +4,13 @@ import attrs from flopy4.mf6.exchange import Exchange +from flopy4.mf6.model import Model +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) class Gwfprt(Exchange): dfn_name: ClassVar[str] = "exg-gwfprt" + + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) diff --git a/flopy4/mf6/exg/gwtgwt.py b/flopy4/mf6/exg/gwtgwt.py index fa0325cb..69d83df1 100644 --- a/flopy4/mf6/exg/gwtgwt.py +++ b/flopy4/mf6/exg/gwtgwt.py @@ -9,8 +9,10 @@ from flopy4.mf6.exchange import Exchange from flopy4.mf6.gwt.mvt import Mvt from flopy4.mf6.item import Item +from flopy4.mf6.model import Model from flopy4.mf6.spec import array, cellid_, child, double, integer, keyword, list_, string from flopy4.mf6.utl.obs import Obs +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) @@ -36,6 +38,8 @@ class Exchangedata(Item): default=None, empty_is_none=True, optional=True, when="boundnames" ) + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) gwfmodelname1: str = string( default=None, block="options", diff --git a/flopy4/mf6/exg/olfgwf.py b/flopy4/mf6/exg/olfgwf.py index 2b0e89b6..41dd949c 100644 --- a/flopy4/mf6/exg/olfgwf.py +++ b/flopy4/mf6/exg/olfgwf.py @@ -5,8 +5,10 @@ from flopy4.mf6.exchange import Exchange from flopy4.mf6.item import Item +from flopy4.mf6.model import Model from flopy4.mf6.spec import cellid_, child, double, integer, keyword, list_ from flopy4.mf6.utl.obs import Obs +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) @@ -22,6 +24,8 @@ class Exchangedata(Item): bedleak: float = double() cfact: float = double() + exgmnamea: Model | str | None = reference(many=False, required=True) + exgmnameb: Model | str | None = reference(many=False, required=True) print_input: bool = keyword( default=False, block="options", diff --git a/flopy4/mf6/ims.py b/flopy4/mf6/ims.py index 0aef6071..8f61d29b 100644 --- a/flopy4/mf6/ims.py +++ b/flopy4/mf6/ims.py @@ -4,9 +4,11 @@ import attrs +from flopy4.mf6.model import Model from flopy4.mf6.record import Record from flopy4.mf6.solution import Solution from flopy4.mf6.spec import double, file, integer, record, string +from flopy4.spec import reference @attrs.define(kw_only=True, slots=False) @@ -26,6 +28,7 @@ class Rclose(Record): inner_rclose: float = double() rclose_option: Optional[str] = string(default=None, optional=True) + models: list[Model | str] = reference() print_option: Optional[str] = string( default=None, block="options", diff --git a/flopy4/mf6/solution.py b/flopy4/mf6/solution.py index 331f41dd..1be3a76c 100644 --- a/flopy4/mf6/solution.py +++ b/flopy4/mf6/solution.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING, ClassVar import attrs +from attrs import Attribute from flopy4.mf6.package import Package -from flopy4.spec import reference if TYPE_CHECKING: from flopy4.mf6.model import Model @@ -12,12 +12,16 @@ @attrs.define(kw_only=True, slots=False) class Solution(Package, ABC): + """A solution. Its `models` (generated, from its row in the + simulation's name file) are the models it solves, as objects or names. + The simulation owns its models; a solution only refers to them. If a + simulation has one solution and it lists no models, it solves them + all. A solution given no parent joins its models' simulation, if they + have one.""" + slntype: ClassVar[str] = "sln" - models: "list[Model | str]" = reference(check=lambda sln, m: sln._cant_solve(m)) - """The models this solution solves, as objects or names. The simulation - owns its models; a solution only refers to them. If a simulation has - one solution and it lists no models, it solves them all. A solution - given no parent joins its models' simulation, if they have one.""" + if TYPE_CHECKING: + models: "list[Model | str]" def solves(self, model: "Model") -> bool: """Whether this kind of solution can solve the model's type: EMS @@ -25,6 +29,9 @@ def solves(self, model: "Model") -> bool: is_prt = type(model).dfn_name.startswith("prt") # type: ignore[attr-defined] return is_prt == (self.slntype == "ems") + def _reference_problem(self, field: Attribute, model) -> "str | None": + return self._cant_solve(model) + def _cant_solve(self, model: "Model") -> "str | None": """Why this solution can't solve the model, or None if it can.""" if self.solves(model): diff --git a/flopy4/mf6/utils/codegen/make.py b/flopy4/mf6/utils/codegen/make.py index 1a0fd2bb..2ee2dad3 100644 --- a/flopy4/mf6/utils/codegen/make.py +++ b/flopy4/mf6/utils/codegen/make.py @@ -1303,6 +1303,65 @@ def _child_field_spec( ), [f"from {_component_module(target)} import {cls}"] +# Reference fields' Python names, where they aren't the DFN's: a solution's +# models kept the name they had before the DFNs keyed them. +_REFERENCE_NAMES = {"slnmnames": "models"} + + +def _binding_row(component: Component, dfns: Mapping[str, Component]) -> Record | None: + """The row naming the component in its parent's name file (an + exchange's ``exgtype exgfile exgmnamea exgmnameb`` in the simulation's + exchanges block), or None. Such a row's file column picks its + component by a ``component_ftype`` column.""" + for parent in dfns.values(): + if not _is_child_of(component, parent): + continue + for _, f in filters.flat_fields(parent): + if not isinstance(f, ListField) or not isinstance(f.item, Record): + continue + found = _find_link(f) + if found is None or found[1].component_ftype is None: + continue + if component.name in resolve_link(parent, found[0], found[1], dfns): + return f.item + return None + + +def _reference_specs( + component: Component, dfns: Mapping[str, Component] | None +) -> tuple[list[FieldSpec], list[str]]: + """The component's reference fields (see `flopy4.spec.reference`) and + their imports: the columns of its row in its parent's name file keyed + (``fk``) to the parent's models, in row order. An exchange's + ``exgmnamea`` and ``exgmnameb``, a solution's ``slnmnames``.""" + row = _binding_row(component, dfns) if dfns is not None else None + if row is None: + return [], [] + specs = [] + for name, m in (row.fields or {}).items(): + if not (fk := getattr(m, "fk", None)): + continue + if fk.partition(".")[0] != "models": + raise NotImplementedError(f"{component.name}.{name}: a reference to {fk}") + if isinstance(m, Array): + annotation, call = "list[Model | str]", "reference()" + else: + annotation = "Model | str | None" + call = "reference(many=False)" if m.optional else "reference(many=False, required=True)" + specs.append( + FieldSpec( + dfn_name=name, + py_name=_REFERENCE_NAMES.get(name, filters.safe_name(name)), + type_annotation=annotation, + spec_call=call, + generatable=True, + ) + ) + if not specs: + return [], [] + return specs, ["from flopy4.mf6.model import Model", "from flopy4.spec import reference"] + + # Package types sharing one model field: a model has one discretization. _SLOTS = {"DIS6": "dis", "DISV6": "dis", "DISU6": "dis"} @@ -1466,6 +1525,10 @@ def build_component_spec( _linked_dims = {bp.dim_attr for bp in block_properties if bp.dim_is_dfn_declared} extra_imports: list[str] = [] # child fields' imports + # reference fields first, in row order + _reference_fields, _reference_imports = _reference_specs(component, dfns) + prefix_specs.extend(_reference_fields) + extra_imports.extend(_reference_imports) _period_item: str | None = None # element type of the fill-forward block's list _readarray_period_fields: list[FieldV3] = [] # READARRAY period fields (CHDG, DRNG …) _dynamically_named_period_fields: list[tuple] = [] # (list, array, fk): RCHA's aux diff --git a/flopy4/spec.py b/flopy4/spec.py index d2e60adf..1191c8fa 100644 --- a/flopy4/spec.py +++ b/flopy4/spec.py @@ -2,9 +2,6 @@ Wrap `attrs` specification utilities. """ -from collections.abc import Callable -from typing import Any - import attrs from attrs import Attribute from attrs import fields_dict as attrs_fields_dict @@ -78,11 +75,7 @@ def _check_reference(instance, attribute, value) -> None: raise TypeError(f"{attribute.name} takes a component or a name, not {type(value).__name__}") -def reference( - many: bool = True, - check: "Callable[[Any, Any], str | None] | None" = None, - required: bool = False, -): +def reference(many: bool = True, required: bool = False): """Models this component refers to but doesn't own (e.g. the models a solution solves), given as objects or names: a list of them, or with `many=False` one (or None). The simulation owns its models; a @@ -92,9 +85,12 @@ def reference( parent joins its references' simulation (see `Component.__attrs_post_init__`). - When written, a reference must be one of the simulation's models, and - `check(component, model)`, if given, must return no problem with it. A - single one must also be given if `required`.""" + Generated from the DFNs: a column of the component's row in its + parent's name file keyed to the parent's models (``fk``) is a + reference, in row order. When written, a reference must be one of the + simulation's models, and the component's `_reference_problem` must + find no problem with it. A single one must also be given if + `required`.""" shape: dict = ( {"factory": list, "converter": _to_reference_list} if many @@ -102,7 +98,7 @@ def reference( ) return attrs.field( **shape, - metadata={"reference": True, "check": check, "required": required}, + metadata={"reference": True, "many": many, "required": required}, repr=lambda v: repr(reference_names(v)), eq=reference_names, ) diff --git a/pixi.lock b/pixi.lock index 8e186456..8eb10b25 100644 --- a/pixi.lock +++ b/pixi.lock @@ -106,7 +106,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/70/746ab7140121dcf04bade9ab7d2e65b6d5eb5b23bb7dc04b7ad22742ca0f/xugrid-0.15.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/4e/23e0009b6d5d415c223ea38fcf1e38dc9b0fc6d3ff15b5c5090dcc1f6d88/sparse-0.19.2-py2.py3-none-any.whl @@ -255,7 +255,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/07/70/746ab7140121dcf04bade9ab7d2e65b6d5eb5b23bb7dc04b7ad22742ca0f/xugrid-0.15.3-py3-none-any.whl @@ -404,7 +404,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/70/746ab7140121dcf04bade9ab7d2e65b6d5eb5b23bb7dc04b7ad22742ca0f/xugrid-0.15.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/4e/23e0009b6d5d415c223ea38fcf1e38dc9b0fc6d3ff15b5c5090dcc1f6d88/sparse-0.19.2-py2.py3-none-any.whl @@ -547,7 +547,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/07/70/746ab7140121dcf04bade9ab7d2e65b6d5eb5b23bb7dc04b7ad22742ca0f/xugrid-0.15.3-py3-none-any.whl @@ -709,7 +709,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -992,7 +992,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -1273,7 +1273,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -1548,7 +1548,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl @@ -1838,7 +1838,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/01/f9/575c8d760eae1fc99651b7cc5efd96ad5379ca4d6b53750b0fb4fe983f34/imagesize-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -2105,7 +2105,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/01/f9/575c8d760eae1fc99651b7cc5efd96ad5379ca4d6b53750b0fb4fe983f34/imagesize-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -2373,7 +2373,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/01/f9/575c8d760eae1fc99651b7cc5efd96ad5379ca4d6b53750b0fb4fe983f34/imagesize-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -2634,7 +2634,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/01/f9/575c8d760eae1fc99651b7cc5efd96ad5379ca4d6b53750b0fb4fe983f34/imagesize-2.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -2914,7 +2914,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-9_cp311.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl @@ -3165,7 +3165,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl @@ -3417,7 +3417,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz @@ -3663,7 +3663,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl @@ -3922,7 +3922,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-9_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -4170,7 +4170,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl @@ -4419,7 +4419,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/4a/78c6285577c375e7cf27277ea8ee6961224327f1e1a0c44af5f17f23635c/scipy-1.18.1-cp312-cp312-macosx_12_0_arm64.whl @@ -4662,7 +4662,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/8b/b83f7780dad22e0878e4c7bd9158ebd24ed12bc3d5e3a471cd0576f77ded/pyzmq-27.2.0-cp312-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl @@ -4917,7 +4917,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl @@ -5167,7 +5167,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-ha6c374e_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-hbc1a06c_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl @@ -5418,7 +5418,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl @@ -5663,7 +5663,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda - pypi: ./ - - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f + - pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 - pypi: https://files.pythonhosted.org/packages/02/8b/b83f7780dad22e0878e4c7bd9158ebd24ed12bc3d5e3a471cd0576f77ded/pyzmq-27.2.0-cp312-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -10927,7 +10927,7 @@ packages: - build ; extra == 'build' - twine ; extra == 'build' requires_python: '>=3.11,<3.14' -- pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#9a1662ef87c006346b5769f856f730bad35f218f +- pypi: git+https://github.com/MODFLOW-ORG/modflow-devtools.git#b7c43566bb2e950a82c34636aefbc3fda701ca08 name: modflow-devtools version: 1.10.0.dev1 requires_dist: diff --git a/test/mf6/test_mf6_codegen.py b/test/mf6/test_mf6_codegen.py index 1c241390..224a863a 100644 --- a/test/mf6/test_mf6_codegen.py +++ b/test/mf6/test_mf6_codegen.py @@ -416,6 +416,29 @@ def test_simulation_spec(all_dfns): assert {"continue_", "nocheck", "maxerrors", "mxiter"} <= set(types) +@pytest.mark.parametrize( + "name, expected", + [ + ( + "exg-gwfgwt", + [ + ("exgmnamea", "Model | str | None", "reference(many=False, required=True)"), + ("exgmnameb", "Model | str | None", "reference(many=False, required=True)"), + ], + ), + ("sln-ims", [("models", "list[Model | str]", "reference()")]), + ("gwf-chd", []), + ], +) +def test_reference_fields_from_keys(all_dfns, name, expected): + """A component's columns in its row in the simulation's name file that + are keyed to its models become reference fields, first, in row order.""" + spec = build_component_spec(all_dfns[name], root=Path("/fake/mf6"), dfns=all_dfns) + refs = [f for f in spec.fields if f.spec_call.startswith("reference(")] + assert [(f.py_name, f.type_annotation, f.spec_call) for f in refs] == expected + assert spec.fields[: len(refs)] == refs + + def test_model_spec(all_dfns): root = Path("/fake/mf6") spec = build_component_spec(all_dfns["gwt-nam"], root=root, dfns=all_dfns) diff --git a/test/mf6/test_mf6_grammar_gen.py b/test/mf6/test_mf6_grammar_gen.py index 4517e9ec..71fbaf2e 100644 --- a/test/mf6/test_mf6_grammar_gen.py +++ b/test/mf6/test_mf6_grammar_gen.py @@ -381,7 +381,7 @@ def test_make_grammar_with_dynamically_named_arrays(tmp_path): item = Record( name="aux", fields={ - "auxname": String(name="auxname", tagged=False, fk="options.auxiliary"), + "auxname": String(name="auxname", tagged=False, fk="options.auxiliary", dynamic=True), "aux": Array(name="aux", dtype="double", shape=["ncpl"], tagged=False), }, ) diff --git a/test/mf6/test_mixin_fields.py b/test/mf6/test_mixin_fields.py index b4ff709f..fe0e2544 100644 --- a/test/mf6/test_mixin_fields.py +++ b/test/mf6/test_mixin_fields.py @@ -119,6 +119,36 @@ class _RefsLacking(_Comprehensions): refs: list[_Child] = attrs.Factory(list) +@attrs.define +class _Base: + pass + + +@attrs.define +class _Full(_Base): + name: str = "" + + +@attrs.define +class _Bare(_Base): + pass + + +class _ReadsBase: + def names(self): + return [b.name for b in self.bases] + + +@attrs.define +class _HasBases(_ReadsBase): + bases: list[_Base] = attrs.Factory(list) + + +def test_mixin_gaps_through_base_class(): + # the base lacks `name`; of its subclasses, only _Bare does too + assert mixin_gaps(_HasBases, _ReadsBase) == ["_ReadsBase.names: no _Bare.name"] + + def test_mixin_gaps_through_union_with_plain_type(): # an item might be a str, which has `lower` assert "_Child.lower" not in str(mixin_gaps(_Refs, _Comprehensions))