diff --git a/.readthedocs.yml b/.readthedocs.yml index 99b914b..c1325d5 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -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: . diff --git a/HISTORY.md b/HISTORY.md index 96524f3..0f7d8d9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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 diff --git a/README.md b/README.md index 75711ea..1ae2c7d 100644 --- a/README.md +++ b/README.md @@ -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) +Python development tools for MODFLOW 6. - [Use cases](#use-cases) - [Requirements](#requirements) diff --git a/docs/conf.py b/docs/conf.py index dbe6721..ba8c65b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 diff --git a/modflow_devtools/__init__.py b/modflow_devtools/__init__.py index abd3212..a37151c 100644 --- a/modflow_devtools/__init__.py +++ b/modflow_devtools/__init__.py @@ -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__ = "jdhughes@usgs.gov" __status__ = "Production" diff --git a/modflow_devtools/fixtures.py b/modflow_devtools/fixtures.py index 66da777..1fa695c 100644 --- a/modflow_devtools/fixtures.py +++ b/modflow_devtools/fixtures.py @@ -1,3 +1,4 @@ +import random from collections import OrderedDict from itertools import groupby from os import PathLike, environ @@ -10,7 +11,8 @@ pytest = import_optional_dependency("pytest") -# temporary directory fixtures + +# fixtures @pytest.fixture(scope="function") @@ -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(): @@ -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(): @@ -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(): @@ -72,7 +74,7 @@ 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(): @@ -80,16 +82,26 @@ def session_tmpdir(tmpdir_factory, request) -> 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): @@ -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 " @@ -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( diff --git a/modflow_devtools/test/test_fixtures.py b/modflow_devtools/test/test_fixtures.py index f553823..c5364e6 100644 --- a/modflow_devtools/test/test_fixtures.py +++ b/modflow_devtools/test/test_fixtures.py @@ -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): @@ -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", @@ -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() @@ -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"]) @@ -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" @@ -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 @@ -234,7 +240,7 @@ def pytest_terminal_summary(self, terminalreporter): def test_meta(): args = [ - f"{__file__}", + __file__, "-v", "-s", "-k", @@ -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 diff --git a/version.txt b/version.txt index 1cc5f65..867e524 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.2.0 \ No newline at end of file