From 81a258cf4155a1aeefb9a9c5323e8e264fbe472d Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Thu, 25 Jul 2024 23:44:07 -0400 Subject: [PATCH] misc cleanup and fixes --- flopy4/array.py | 2 +- flopy4/block.py | 15 +++++---------- flopy4/compound.py | 15 ++++++--------- flopy4/package.py | 34 +++++++++++++++++++++++----------- flopy4/param.py | 27 ++++++++++++++------------- flopy4/scalar.py | 15 ++++++++++----- test/test_package.py | 5 ++++- 7 files changed, 63 insertions(+), 50 deletions(-) diff --git a/flopy4/array.py b/flopy4/array.py index 26a122d..e1d2971 100644 --- a/flopy4/array.py +++ b/flopy4/array.py @@ -338,7 +338,7 @@ def how(self): return self._how - def write(self, f): + def write(self, f, **kwargs): # todo pass diff --git a/flopy4/block.py b/flopy4/block.py index 683740a..f8fae1a 100644 --- a/flopy4/block.py +++ b/flopy4/block.py @@ -11,11 +11,6 @@ from flopy4.utils import find_upper, strip -def single_keystring(members): - params = list(members.values()) - return len(params) == 1 and isinstance(params[0], MFKeystring) - - def get_param(members, name, block): ks = [m for m in members.values() if isinstance(m, MFKeystring)] if len(ks) == 1: @@ -84,9 +79,12 @@ def __init__(self, name=None, index=None, params=None): super().__init__(params) def __getattribute__(self, name: str) -> Any: - if name in ["data", "params"]: + if name == "data": return super().__getattribute__(name) + if name == "params": + return MFParams({k: v.value for k, v in self.data.items()}) + param = self.data.get(name) return ( param.value @@ -94,9 +92,6 @@ def __getattribute__(self, name: str) -> Any: else super().__getattribute__(name) ) - def __repr__(self): - return pformat({k: v for k, v in self.data.items()}) - def __str__(self): buffer = StringIO() self.write(buffer) @@ -166,7 +161,7 @@ def __init__(self, blocks=None): setattr(self, key, block) def __repr__(self): - return pformat({k: repr(v) for k, v in self.data.items()}) + return pformat(self.data) def write(self, f): """Write the blocks to file.""" diff --git a/flopy4/compound.py b/flopy4/compound.py index 294ef45..70d960f 100644 --- a/flopy4/compound.py +++ b/flopy4/compound.py @@ -2,7 +2,6 @@ from collections.abc import Mapping from dataclasses import asdict from io import StringIO -from pprint import pformat from typing import Any, Dict from flopy4.param import MFParam, MFParams, MFReader @@ -58,13 +57,10 @@ def __init__( def __get__(self, obj, type=None): return self - def __repr__(self): - return pformat(self.data) - @property def params(self) -> MFParams: """Component parameters.""" - return self.data + return MFParams(self.data) @property def value(self) -> Mapping[str, Any]: @@ -150,11 +146,12 @@ def parse(line, params, **kwargs) -> Dict[str, MFScalar]: return loaded - def write(self, f): + def write(self, f, **kwargs): + """Write the record to file.""" f.write(f"{PAD}{self.name.upper()}") last = len(self) - 1 for i, param in enumerate(self.data.values()): - param.write(f, newline=i == last) + param.write(f, newline=i == last, **kwargs) class MFKeystring(MFCompound): @@ -225,6 +222,6 @@ def load(cls, f, params, **kwargs) -> "MFKeystring": return cls(loaded, **kwargs) - def write(self, f): + def write(self, f, **kwargs): """Write the keystring to file.""" - super().write(f) + super().write(f, **kwargs) diff --git a/flopy4/package.py b/flopy4/package.py index 1aea7ea..5365a7d 100644 --- a/flopy4/package.py +++ b/flopy4/package.py @@ -1,7 +1,6 @@ from abc import ABCMeta from io import StringIO from itertools import groupby -from pprint import pformat from typing import Any from flopy4.block import MFBlock, MFBlockMeta, MFBlocks @@ -10,9 +9,12 @@ def get_block(pkg_name, block_name, params): - return MFBlockMeta( - f"{pkg_name.title()}{block_name.title()}Block", (MFBlock,), params - )(params=params, name=block_name) + cls = MFBlockMeta( + f"{pkg_name.title()}{block_name.title()}Block", + (MFBlock,), + params.copy(), + ) + return cls(params=params, name=block_name) class MFPackageMeta(type): @@ -70,21 +72,31 @@ class MFPackage(MFBlocks, metaclass=MFPackageMappingMeta): def __init__(self, blocks=None): super().__init__(blocks) - def __repr__(self): - return pformat(self.data) - def __str__(self): buffer = StringIO() self.write(buffer) return buffer.getvalue() def __getattribute__(self, name: str) -> Any: - if name in ["data", "params", "blocks"]: + if name == "data": return super().__getattribute__(name) - block = self.data.get(name) - if block is not None: - return block + if name == "blocks": + return MFBlocks(self.data) + + if name == "params": + # todo cache this + return MFParams( + { + param_name: param + for block in self.values() + for param_name, param in block.items() + } + ) + + # shortcut to block value + if name in self: + return self[name] # shortcut to parameter value for instance attribute. # the class attribute is the parameter specification. diff --git a/flopy4/param.py b/flopy4/param.py index ef263a7..437becb 100644 --- a/flopy4/param.py +++ b/flopy4/param.py @@ -84,8 +84,11 @@ def with_block(self, block) -> "MFParamSpec": class MFParam(MFParamSpec): """ MODFLOW 6 input parameter. Can be a scalar or compound of - scalars, an array, or a list (i.e. a table). `MFParameter` - classes play a dual role: first, to define the blocks that + scalars, an array, or a list (i.e. a table). + + Notes + ----- + This class plays a dual role: first, to define blocks that specify the input required for MF6 components; and second, as a data access layer by which higher components (blocks, packages, etc) can read/write parameters. The former is a @@ -93,10 +96,8 @@ class MFParam(MFParamSpec): generated from DFNs) while the latter happens at runtime, but both APIs are user-facing; the user can first inspect a package's specification via class attributes, then load - an input file and inspect the package data. + an input file and inspect package data via instance attrs. - Notes - ----- Specification attributes are set at import time. A parent block or package defines parameters as class attributes, including a description, whether the parameter is optional, @@ -149,11 +150,6 @@ def __init__( default_value=default_value, ) - def __repr__(self): - return ( - super().__repr__() if self.value is None else pformat(self.value) - ) - def __str__(self): buffer = StringIO() self.write(buffer) @@ -165,6 +161,11 @@ def value(self) -> Optional[Any]: """Get the parameter's value, if loaded.""" pass + @abstractmethod + def write(self, f, **kwargs): + """Write the parameter to file.""" + pass + class MFParams(UserDict): """ @@ -178,9 +179,9 @@ def __init__(self, params=None): setattr(self, key, param) def __repr__(self): - return pformat({k: repr(v) for k, v in self.data.items()}) + return pformat(self.data) - def write(self, f): + def write(self, f, **kwargs): """Write the parameters to file.""" for param in self.data.values(): - param.write(f) + param.write(f, **kwargs) diff --git a/flopy4/scalar.py b/flopy4/scalar.py index 3d0528c..729e44b 100644 --- a/flopy4/scalar.py +++ b/flopy4/scalar.py @@ -118,7 +118,8 @@ def load(cls, f, **kwargs) -> "MFKeyword": kwargs["name"] = line return cls(value=True, **kwargs) - def write(self, f, newline=True): + def write(self, f, **kwargs): + newline = kwargs.pop("newline", True) if self.value: f.write( f"{PAD}" f"{self.name.upper()}" + ("\n" if newline else "") @@ -180,7 +181,8 @@ def load(cls, f, **kwargs) -> "MFInteger": kwargs["name"] = words[0] return cls(value=int(words[1]), **kwargs) - def write(self, f, newline=True): + def write(self, f, **kwargs): + newline = kwargs.pop("newline", True) f.write( f"{PAD}" f"{self.name.upper()} " @@ -243,7 +245,8 @@ def load(cls, f, **kwargs) -> "MFDouble": kwargs["name"] = words[0] return cls(value=float(words[1]), **kwargs) - def write(self, f, newline=True): + def write(self, f, **kwargs): + newline = kwargs.pop("newline", True) f.write( f"{PAD}" f"{self.name.upper()} " @@ -306,7 +309,8 @@ def load(cls, f, **kwargs) -> "MFString": kwargs["name"] = words[0] return cls(value=words[1], **kwargs) - def write(self, f, newline=True): + def write(self, f, **kwargs): + newline = kwargs.pop("newline", True) f.write( f"{PAD}" f"{self.name.upper()} " @@ -381,7 +385,8 @@ def load(cls, f, **kwargs) -> "MFFilename": **kwargs, ) - def write(self, f, newline=True): + def write(self, f, **kwargs): + newline = kwargs.pop("newline", True) f.write( f"{PAD}{self.name.upper()} " f"{self.inout.value.upper()} " diff --git a/test/test_package.py b/test/test_package.py index f2ec8d0..3a55e82 100644 --- a/test/test_package.py +++ b/test/test_package.py @@ -316,8 +316,11 @@ def test_load_gwfic(tmp_path): with open(fpth, "r") as f: gwfic = TestGwfIc.load(f) + assert len(TestGwfIc.blocks) == 2 + assert len(TestGwfIc.params) == 3 + assert len(gwfic.blocks) == 2 - assert len(gwfic.params) == 3 + assert len(gwfic.params) == 2 # only two params loaded # instance attributes: shortcut access to param values assert isinstance(gwfic.export_array_ascii, bool)