Skip to content

Commit

Permalink
Merge pull request #113 from MODFLOW-USGS/v1.2.0
Browse files Browse the repository at this point in the history
Release 1.2.0
  • Loading branch information
wpbonelli authored Sep 12, 2023
2 parents 79c8f3b + 9810437 commit ccf47eb
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 33 deletions.
5 changes: 4 additions & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
version: 2
build:
os: "ubuntu-22.04"
tools:
python: "3.8"
sphinx:
configuration: docs/conf.py
formats:
- pdf
python:
version: "3.8"
install:
- method: pip
path: .
Expand Down
6 changes: 6 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### Version 1.2.0

#### New features

* [feat(fixtures)](https://github.com/MODFLOW-USGS/modflow-devtools/commit/a41caa75f8519780c7ee60daf61d8225b4380dd5): Add use_pandas pytest fixture and --pandas CLI arg (#112). Committed by wpbonelli on 2023-09-12.

### Version 1.1.0

#### Refactoring
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
# MODFLOW developer tools

[![CI](https://github.com/MODFLOW-USGS/modflow-devtools/actions/workflows/ci.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow-devtools/actions/workflows/ci.yml)
[![Documentation Status](https://readthedocs.org/projects/modflow-devtools/badge/?version=latest)](https://modflow-devtools.readthedocs.io/en/latest/?badge=latest)
[![GitHub contributors](https://img.shields.io/github/contributors/MODFLOW-USGS/modflow-devtools)](https://img.shields.io/github/contributors/MODFLOW-USGS/modflow-devtools)
[![GitHub tag](https://img.shields.io/github/tag/MODFLOW-USGS/modflow-devtools.svg)](https://github.com/MODFLOW-USGS/modflow-devtools/tags/latest)

[![PyPI License](https://img.shields.io/pypi/l/modflow-devtools)](https://pypi.python.org/pypi/modflow-devtools)
[![PyPI Status](https://img.shields.io/pypi/status/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools)
[![PyPI Format](https://img.shields.io/pypi/format/modflow-devtools)](https://pypi.python.org/pypi/modflow-devtools)
[![PyPI Version](https://img.shields.io/pypi/v/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools)
[![PyPI Versions](https://img.shields.io/pypi/pyversions/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools)
[![PyPI Status](https://img.shields.io/pypi/status/modflow-devtools.png)](https://pypi.python.org/pypi/modflow-devtools)
[![CI](https://github.com/MODFLOW-USGS/modflow-devtools/actions/workflows/ci.yml/badge.svg)](https://github.com/MODFLOW-USGS/modflow-devtools/actions/workflows/ci.yml)
[![Documentation Status](https://readthedocs.org/projects/modflow-devtools/badge/?version=latest)](https://modflow-devtools.readthedocs.io/en/latest/?badge=latest)

Python development tools for MODFLOW 6.
[![Anaconda License](https://anaconda.org/conda-forge/modflow-devtools/badges/license.svg)](https://anaconda.org/conda-forge/modflow-devtools/badges/license.svg)
[![Anaconda Version](https://anaconda.org/conda-forge/modflow-devtools/badges/version.svg)](https://anaconda.org/conda-forge/modflow-devtools)
[![Anaconda Updated](https://anaconda.org/conda-forge/modflow-devtools/badges/latest_release_date.svg)](https://anaconda.org/conda-forge/modflow-devtools)

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

Python development tools for MODFLOW 6.

- [Use cases](#use-cases)
- [Requirements](#requirements)
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

project = "modflow-devtools"
author = "MODFLOW Team"
release = "1.1.0"
release = "1.2.0"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
4 changes: 2 additions & 2 deletions modflow_devtools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__author__ = "Joseph D. Hughes"
__date__ = "Aug 12, 2023"
__version__ = "1.1.0"
__date__ = "Sep 12, 2023"
__version__ = "1.2.0"
__maintainer__ = "Joseph D. Hughes"
__email__ = "[email protected]"
__status__ = "Production"
Expand Down
40 changes: 31 additions & 9 deletions modflow_devtools/fixtures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import random
from collections import OrderedDict
from itertools import groupby
from os import PathLike, environ
Expand All @@ -10,7 +11,8 @@

pytest = import_optional_dependency("pytest")

# temporary directory fixtures

# fixtures


@pytest.fixture(scope="function")
Expand All @@ -23,7 +25,7 @@ def function_tmpdir(tmpdir_factory, request) -> Path:
temp = Path(tmpdir_factory.mktemp(node))
yield Path(temp)

keep = request.config.getoption("--keep")
keep = request.config.option.KEEP
if keep:
path = Path(keep) / temp.name
if path.is_dir():
Expand All @@ -46,7 +48,7 @@ def class_tmpdir(tmpdir_factory, request) -> Path:
temp = Path(tmpdir_factory.mktemp(request.cls.__name__))
yield temp

keep = request.config.getoption("--keep")
keep = request.config.option.KEEP
if keep:
path = Path(keep) / temp.name
if path.is_dir():
Expand All @@ -59,7 +61,7 @@ def module_tmpdir(tmpdir_factory, request) -> Path:
temp = Path(tmpdir_factory.mktemp(request.module.__name__))
yield temp

keep = request.config.getoption("--keep")
keep = request.config.option.KEEP
if keep:
path = Path(keep) / temp.name
if path.is_dir():
Expand All @@ -72,24 +74,34 @@ def session_tmpdir(tmpdir_factory, request) -> Path:
temp = Path(tmpdir_factory.mktemp(request.session.name))
yield temp

keep = request.config.getoption("--keep")
keep = request.config.option.KEEP
if keep:
path = Path(keep) / temp.name
if path.is_dir():
rmtree(path)
copytree(temp, path)


# environment-dependent fixtures


@pytest.fixture
def repos_path() -> Optional[Path]:
"""Path to directory containing test model and example repositories"""
return environ.get("REPOS_PATH", None)


# pytest configuration hooks
@pytest.fixture
def use_pandas(request):
pandas = request.config.option.PANDAS
if pandas == "yes":
return True
elif pandas == "no":
return False
elif pandas == "random":
return random.randint(0, 1) == 0
else:
raise ValueError(f"Unsupported value for --pandas: {pandas}")


# configuration hooks


def pytest_addoption(parser):
Expand All @@ -98,6 +110,7 @@ def pytest_addoption(parser):
"--keep",
action="store",
default=None,
dest="KEEP",
help="Move the contents of temporary test directories to correspondingly named subdirectories at the given "
"location after tests complete. This option can be used to exclude test results from automatic cleanup, "
"e.g. for manual inspection. The provided path is created if it does not already exist. An error is "
Expand Down Expand Up @@ -144,6 +157,15 @@ def pytest_addoption(parser):
help="Select a subset of packages to run.",
)

parser.addoption(
"-P",
"--pandas",
action="store",
default="yes",
dest="PANDAS",
help="Package input data can be provided as either pandas dataframes or numpy recarrays. By default, pandas dataframes are used. To test with numpy recarrays, use 'no'. To randomize selection (per test), use 'random'.",
)


def pytest_configure(config):
config.addinivalue_line(
Expand Down
73 changes: 58 additions & 15 deletions modflow_devtools/test/test_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,18 @@ def test_function_scoped_tmpdir_slash_in_name(function_tmpdir, name):


class TestClassScopedTmpdir:
filename = "hello.txt"
fname = "hello.txt"

@pytest.fixture(autouse=True)
def setup(self, class_tmpdir):
with open(class_tmpdir / self.filename, "w") as file:
with open(class_tmpdir / self.fname, "w") as file:
file.write("hello, class-scoped tmpdir")

def test_class_scoped_tmpdir(self, class_tmpdir):
assert isinstance(class_tmpdir, Path)
assert class_tmpdir.is_dir()
assert self.__class__.__name__ in class_tmpdir.stem
assert Path(class_tmpdir / self.filename).is_file()
assert Path(class_tmpdir / self.fname).is_file()


def test_module_scoped_tmpdir(module_tmpdir):
Expand All @@ -74,38 +74,38 @@ def test_session_scoped_tmpdir(session_tmpdir):

# test CLI arguments --keep (-K) and --keep-failed for temp dir fixtures

FILE_NAME = "hello.txt"
test_keep_fname = "hello.txt"


@pytest.mark.meta("test_keep")
def test_keep_function_scoped_tmpdir_inner(function_tmpdir):
with open(function_tmpdir / FILE_NAME, "w") as f:
with open(function_tmpdir / test_keep_fname, "w") as f:
f.write("hello, function-scoped tmpdir")


@pytest.mark.meta("test_keep")
class TestKeepClassScopedTmpdirInner:
def test_keep_class_scoped_tmpdir_inner(self, class_tmpdir):
with open(class_tmpdir / FILE_NAME, "w") as f:
with open(class_tmpdir / test_keep_fname, "w") as f:
f.write("hello, class-scoped tmpdir")


@pytest.mark.meta("test_keep")
def test_keep_module_scoped_tmpdir_inner(module_tmpdir):
with open(module_tmpdir / FILE_NAME, "w") as f:
with open(module_tmpdir / test_keep_fname, "w") as f:
f.write("hello, module-scoped tmpdir")


@pytest.mark.meta("test_keep")
def test_keep_session_scoped_tmpdir_inner(session_tmpdir):
with open(session_tmpdir / FILE_NAME, "w") as f:
with open(session_tmpdir / test_keep_fname, "w") as f:
f.write("hello, session-scoped tmpdir")


@pytest.mark.parametrize("arg", ["--keep", "-K"])
def test_keep_function_scoped_tmpdir(function_tmpdir, arg):
inner_fn = test_keep_function_scoped_tmpdir_inner.__name__
file_path = Path(function_tmpdir / f"{inner_fn}0" / FILE_NAME)
file_path = Path(function_tmpdir / f"{inner_fn}0" / test_keep_fname)
args = [
__file__,
"-v",
Expand Down Expand Up @@ -144,7 +144,9 @@ def test_keep_class_scoped_tmpdir(tmp_path, arg):
]
assert pytest.main(args) == ExitCode.OK
assert Path(
tmp_path / f"{TestKeepClassScopedTmpdirInner.__name__}0" / FILE_NAME
tmp_path
/ f"{TestKeepClassScopedTmpdirInner.__name__}0"
/ test_keep_fname
).is_file()


Expand All @@ -171,7 +173,7 @@ def test_keep_module_scoped_tmpdir(tmp_path, arg):

print(keep_path)
pprint(list(keep_path.glob("*")))
assert FILE_NAME in [f.name for f in keep_path.glob("*")]
assert test_keep_fname in [f.name for f in keep_path.glob("*")]


@pytest.mark.parametrize("arg", ["--keep", "-K"])
Expand All @@ -188,12 +190,14 @@ def test_keep_session_scoped_tmpdir(tmp_path, arg, request):
tmp_path,
]
assert pytest.main(args) == ExitCode.OK
assert Path(tmp_path / f"{request.session.name}0" / FILE_NAME).is_file()
assert Path(
tmp_path / f"{request.session.name}0" / test_keep_fname
).is_file()


@pytest.mark.meta("test_keep_failed")
def test_keep_failed_function_scoped_tmpdir_inner(function_tmpdir):
with open(function_tmpdir / FILE_NAME, "w") as f:
with open(function_tmpdir / test_keep_fname, "w") as f:
f.write("hello, function-scoped tmpdir")

assert False, "oh no"
Expand All @@ -207,7 +211,9 @@ def test_keep_failed_function_scoped_tmpdir(function_tmpdir, keep):
args += ["--keep-failed", function_tmpdir]
assert pytest.main(args) == ExitCode.TESTS_FAILED

kept_file = Path(function_tmpdir / f"{inner_fn}0" / FILE_NAME).is_file()
kept_file = Path(
function_tmpdir / f"{inner_fn}0" / test_keep_fname
).is_file()
assert kept_file if keep else not kept_file


Expand All @@ -234,7 +240,7 @@ def pytest_terminal_summary(self, terminalreporter):

def test_meta():
args = [
f"{__file__}",
__file__,
"-v",
"-s",
"-k",
Expand Down Expand Up @@ -273,3 +279,40 @@ def test_large_test_model(large_test_model):
assert isinstance(large_test_model, Path)
assert large_test_model.is_file()
assert large_test_model.name == "mfsim.nam"


# test pandas fixture

test_pandas_fname = "pandas.txt"


@pytest.mark.meta("test_pandas")
def test_pandas_inner(function_tmpdir, use_pandas):
with open(function_tmpdir / test_pandas_fname, "w") as f:
f.write(str(use_pandas))


@pytest.mark.parametrize("pandas", ["yes", "no", "random"])
@pytest.mark.parametrize("arg", ["--pandas", "-P"])
def test_pandas(pandas, arg, function_tmpdir):
inner_fn = test_pandas_inner.__name__
args = [
__file__,
"-v",
"-s",
"-k",
inner_fn,
arg,
pandas,
"--keep",
function_tmpdir,
"-M",
"test_pandas",
]
assert pytest.main(args) == ExitCode.OK
res = open(next(function_tmpdir.rglob(test_pandas_fname))).readlines()[0]
assert res
if pandas == "yes":
assert "True" in res
elif pandas == "no":
assert "False" in res
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.0
1.2.0

0 comments on commit ccf47eb

Please sign in to comment.