Skip to content

Commit

Permalink
Merge pull request #29 from wpbonelli/attrs-demo
Browse files Browse the repository at this point in the history
gwfoc demo cleanup
  • Loading branch information
wpbonelli authored Sep 4, 2024
2 parents 5d390dc + ee46483 commit a0a112d
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 70 deletions.
35 changes: 11 additions & 24 deletions flopy4/attrs.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from pathlib import Path
from typing import (
Any,
Optional,
TypeVar,
Union,
)

import attr
from attrs import NOTHING, define, field, fields
from cattrs import structure, unstructure
from attrs import NOTHING, asdict, define, field, fields
from cattrs import structure
from numpy.typing import ArrayLike
from pandas import DataFrame

# Enumerate the primitive types to support.
# This is just for reference, not meant to
# be definitive, exclusive, or exhaustive.

Scalar = Union[bool, int, float, str, Path]
"""A scalar input parameter."""
Expand Down Expand Up @@ -109,7 +112,7 @@ def from_dict(cls, d: dict):

def to_dict(self):
"""Convert the context to a dictionary."""
return unstructure(self)
return asdict(self, recurse=True)

def wrap(cls):
setattr(cls, "from_dict", classmethod(from_dict))
Expand All @@ -128,23 +131,6 @@ def wrap(cls):
return wrap(maybe_cls)


def choice(
maybe_cls: Optional[type[T]] = None,
*,
frozen: bool = False,
):
def wrap(cls):
return context(
cls,
frozen=frozen,
)

if maybe_cls is None:
return wrap

return wrap(maybe_cls)


# Utilities


Expand All @@ -170,10 +156,11 @@ def is_frozen(cls: type) -> bool:
return cls.__setattr__ == attr._make._frozen_setattrs


def to_path(val) -> Optional[Path]:
if val is None:
def to_path(value: Any) -> Optional[Path]:
"""Try to convert the value to a `Path`."""
if value is None:
return None
try:
return Path(val).expanduser()
return Path(value).expanduser()
except:
raise ValueError(f"Cannot convert value to Path: {val}")
raise ValueError(f"Can't convert value to Path: {value}")
Empty file added flopy4/converter.py
Empty file.
7 changes: 0 additions & 7 deletions test/test_attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
params,
)

# Records are product types: named, ordered tuples of scalars.
# Records are immutable: they can't be changed, only evolved.


@context(frozen=True)
class Record:
Expand All @@ -42,9 +39,6 @@ class Block:
)


# Keystrings are sum types: discriminated unions of records.


def test_spec():
spec = params(Record)
assert len(spec) == 4
Expand Down Expand Up @@ -90,5 +84,4 @@ def test_usage():
assert astuple(r) == (True, 42, math.pi, None)
assert asdict(r) == {"rb": True, "ri": 42, "rf": math.pi, "rs": None}
with pytest.raises(TypeError):
# non-optional members are required
Record(rb=True)
95 changes: 56 additions & 39 deletions test/test_gwfoc.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
from pathlib import Path
from typing import Dict, List, Literal, Optional, Union

from cattrs import unstructure
import pytest

from flopy4.attrs import context, is_frozen, param, params, to_path

# Define the package input specification.
# Some of this will be generic, and come
# from elsewhere, eventually.

ArrayFormat = Literal["exponential", "fixed", "general", "scientific"]


Expand All @@ -32,6 +28,36 @@ class PrintFormat:
)


@context
class Options:
budget_file: Optional[Path] = param(
description="""
name of the output file to write budget information""",
converter=to_path,
default=None,
)
budget_csv_file: Optional[Path] = param(
description="""
name of the comma-separated value (CSV) output
file to write budget summary information.
A budget summary record will be written to this
file for each time step of the simulation.""",
converter=to_path,
default=None,
)
head_file: Optional[Path] = param(
description="""
name of the output file to write head information.""",
converter=to_path,
default=None,
)
print_format: Optional[PrintFormat] = param(
description="""
specify format for printing to the listing file""",
default=None,
)


@context
class All:
all: bool = param(
Expand Down Expand Up @@ -74,7 +100,7 @@ class Frequency:

# It's awkward to have single-parameter contexts, but
# it's the only way I got `cattrs` to distinguish the
# choices in the union.
# choices in the union. There is likely a better way.


StepSelection = Union[All, First, Last, Steps, Frequency]
Expand All @@ -89,36 +115,6 @@ class OutputControlData:
ocsetting: StepSelection = param()


@context
class Options:
budget_file: Optional[Path] = param(
description="""
name of the output file to write budget information""",
converter=to_path,
default=None,
)
budget_csv_file: Optional[Path] = param(
description="""
name of the comma-separated value (CSV) output
file to write budget summary information.
A budget summary record will be written to this
file for each time step of the simulation.""",
converter=to_path,
default=None,
)
head_file: Optional[Path] = param(
description="""
name of the output file to write head information.""",
converter=to_path,
default=None,
)
print_format: Optional[PrintFormat] = param(
description="""
specify format for printing to the listing file""",
default=None,
)


Period = List[OutputControlData]
Periods = List[Period]

Expand Down Expand Up @@ -148,15 +144,36 @@ def test_spec():
assert ocsetting.type is StepSelection


def test_options():
def test_options_to_dict():
options = Options(
budget_file="some/file/path.cbc",
)
assert isinstance(options.budget_file, Path)
assert len(unstructure(options)) == 4
assert len(options.to_dict()) == 4


def test_output_control_data_from_dict():
# from dict
ocdata = OutputControlData.from_dict(
{
"action": "print",
"variable": "budget",
"ocsetting": {"steps": [1, 3, 5]},
}
)
assert ocdata.action == "print"


@pytest.mark.xfail(reason="todo")
def test_output_control_data_from_tuple():
ocdata = OutputControlData.from_tuple(
("print", "budget", "steps", 1, 3, 5)
)
assert ocdata.action == "print"
assert ocdata.variable == "budget"


def test_gwfoc_structure():
def test_gwfoc_from_dict():
gwfoc = GwfOc.from_dict(
{
"options": {
Expand Down

0 comments on commit a0a112d

Please sign in to comment.