diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b106b80..df6867b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,17 +45,6 @@ jobs: debug: true - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: wntrblm/nox@2023.04.22 - with: - python-versions: "3.12" - - name: Lint - run: nox --non-interactive --error-on-missing-interpreter --session "lint" - - coveralls_finish: needs: build-and-test runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 951397c..edfe276 100644 --- a/.gitignore +++ b/.gitignore @@ -1,105 +1,7 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ +*.egg-info/ *.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python +.coverage +.nox/ +__pycache__/ build/ -develop-eggs/ dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.nox -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df5d08d..5910e0a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,13 +45,13 @@ repos: rev: v3.15.0 hooks: - id: pyupgrade - args: [--py39-plus] + args: [--py310-plus] -- repo: https://github.com/PyCQA/isort - rev: 5.13.2 +- repo: https://github.com/asottile/reorder-python-imports + rev: v3.12.0 hooks: - - id: isort - files: \.py$ + - id: reorder-python-imports + args: [--py310-plus, --add-import, "from __future__ import annotations"] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 @@ -66,24 +66,14 @@ repos: - id: forbid-new-submodules - id: mixed-line-ending - id: trailing-whitespace - -- repo: https://github.com/regebro/pyroma - rev: "4.2" - hooks: - - id: pyroma - args: ["-d", "--min=10", "."] - additional_dependencies: - - numpy - - cython - -- repo: https://github.com/mgedmin/check-manifest - rev: "0.49" - hooks: - - id: check-manifest - args: ["--ignore=.nox,build", "--no-build-isolation"] - additional_dependencies: - - numpy - - cython + - id: name-tests-test + - id: file-contents-sorter + files: | + (?x)^( + .*requirements(-\w+)?.(in|txt)| + requirements/.*\.txt| + .gitignore + ) - repo: https://github.com/PyCQA/pydocstyle rev: 6.3.0 @@ -99,4 +89,6 @@ repos: rev: v1.8.0 hooks: - id: mypy + language_version: python3.12 additional_dependencies: [types-all] + files: src/.*\.py$ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 5c3f617..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,9 +0,0 @@ -include .pre-commit-config.yaml -include README.rst -include noxfile.py -include pyproject.toml -include requirements.in - -recursive-include requirements *.md *.txt -recursive-include src/bmipy *.typed -recursive-include tests *.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ced3e8 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# BMI for Python + +Python bindings for the CSDMS [Basic Model Interface](https://bmi.readthedocs.io). + +![[Python][pypi-link]][python-badge] +![[DOI][doi-link]][doi-badge] +![[Build Status][build-link]][build-badge] +![[PyPI][pypi-link]][pypi-badge] +![[Build Status][anaconda-link]][anaconda-badge] + +[anaconda-badge]: https://anaconda.org/conda-forge/bmipy/badges/version.svg +[anaconda-link]: https://anaconda.org/conda-forge/bmipy +[build-badge]: https://github.com/csdms/bmi-python/actions/workflows/test.yml/badge.svg +[build-link]: https://github.com/csdms/bmi-python/actions/workflows/test.yml +[doi-badge]: https://zenodo.org/badge/179283861.svg +[doi-link]: https://zenodo.org/badge/latestdoi/179283861 +[pypi-badge]: https://badge.fury.io/py/bmipy.svg +[pypi-link]: https://pypi.org/project/bmipy/ +[python-badge]: https://img.shields.io/pypi/pyversions/bmipy.svg + +## Install + +Install *bmipy* with *pip*, + +```bash +pip install bmipy +``` + +If you're using Anaconda, you can also install *bmipy* +with conda from the *conda-forge* channel, + +```bash +conda install bmipy -c conda-forge +``` + +To build and install *bmipy* from source, + +```bash +pip install git+https://github.com/csdms/bmi-python.git +``` + +## Usage + +```python +from bmipy import Bmi + + +class MyBmi(Bmi): + + def initialize(self, config_file): + # Your implementation goes here +``` + +A complete sample implementation is given in the + +repository. diff --git a/README.rst b/README.rst deleted file mode 100644 index 38250e1..0000000 --- a/README.rst +++ /dev/null @@ -1,65 +0,0 @@ -BMI for Python -============== - -Python bindings for the CSDMS `Basic Model Interface `_. - -.. image:: https://zenodo.org/badge/179283861.svg - :target: https://zenodo.org/badge/latestdoi/179283861 - :alt: DOI - -.. image:: https://github.com/csdms/bmi-python/actions/workflows/test.yml/badge.svg - :target: https://github.com/csdms/bmi-python/actions/workflows/test.yml - :alt: Build Status - -.. image:: https://anaconda.org/conda-forge/bmipy/badges/version.svg - :target: https://anaconda.org/conda-forge/bmipy - :alt: Anaconda-Server Badge - -.. image:: https://anaconda.org/conda-forge/bmipy/badges/platforms.svg - :target: https://anaconda.org/conda-forge/bmipy - :alt: Anaconda-Server Badge - -.. image:: https://anaconda.org/conda-forge/bmipy/badges/downloads.svg - :target: https://anaconda.org/conda-forge/bmipy - :alt: Anaconda-Server Badge - -Install -------- - -Install *bmipy* with *pip*, - -.. code-block:: bash - - $ pip install bmipy - -If you're using Anaconda, you can also install *bmipy* -with conda from the *conda-forge* channel, - -.. code-block:: bash - - $ conda install bmipy -c conda-forge - -To build and install *bmipy* from source, - -.. code-block:: bash - - $ git clone https://github.com/csdms/bmi-python - $ cd bmi-python - $ pip install . - -Usage ------ - -.. code-block:: python - - from bmipy import Bmi - - - class MyBmi(Bmi): - - def initialize(self, config_file): - # Your implementation goes here - -A complete sample implementation is given in the -https://github.com/csdms/bmi-example-python -repository. diff --git a/noxfile.py b/noxfile.py index 882776f..cf83476 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import pathlib import shutil diff --git a/pyproject.toml b/pyproject.toml index 8da82e4..e8fec2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,23 +1,23 @@ -[build-system] -requires = ["setuptools >=61"] -build-backend = "setuptools.build_meta" - [project] name = "bmipy" +requires-python = ">=3.10" description = "Basic Model Interface for Python" -readme = "README.rst" +keywords = [ + "BMI", + "Basic Model Interface", +] authors = [ - {name = "Eric Hutton", email = "huttone@colorado.edu"}, + { name = "Eric Hutton", email = "huttone@colorado.edu" }, +] +maintainers = [ + { name = "Eric Hutton", email = "huttone@colorado.edu" }, ] -keywords = ["BMI", "Basic Model Interface"] -license = {text = "MIT"} classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -25,49 +25,75 @@ classifiers = [ "Topic :: Scientific/Engineering :: Hydrology", "Topic :: Scientific/Engineering :: Physics", ] -requires-python = ">=3.9" dependencies = [ - "click", "numpy", ] -dynamic = ["version"] +dynamic = [ + "version", + "readme", +] + +[project.license] +text = "MIT" + +[project.scripts] +bmipy-render = "bmipy._cmd:main" - [project.scripts] - bmipy-render = "bmipy.cmd:main" +[project.urls] +documentation = "https://bmi.readthedocs.io" +homepage = "https://bmi.readthedocs.io" +repository = "https://github.com/csdms/bmi-python" - [project.urls] - Documentation = "https://bmi.readthedocs.io" - Source = "https://github.com/csdms/bmi-python" +[build-system] +requires = [ + "setuptools >=61", +] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic.readme] +file = "README.md" +content-type = "text/markdown" -[tool] +[tool.setuptools.dynamic.version] +attr = "bmipy._version.__version__" - [tool.setuptools.package-data] - bmipy = ["py.typed"] +[tool.setuptools.packages.find] +where = [ + "src", +] - [tool.setuptools.dynamic] - version = {attr = "bmipy._version.__version__"} +[tool.coverage.run] +relative_files = true - [tool.pytest.ini_options] - minversion = "6.0" - testpaths = ["src", "tests"] - norecursedirs = [".*", "*.egg*", "build", "dist"] - addopts = """ - --ignore setup.py - --tb native - --durations 16 - --strict-markers - --doctest-modules - -vvv - """ - doctest_optionflags = [ - "NORMALIZE_WHITESPACE", - "IGNORE_EXCEPTION_DETAIL", - "ALLOW_UNICODE" - ] +[tool.mypy] +check_untyped_defs = true +disallow_any_generics = true +disallow_incomplete_defs = true +disallow_untyped_defs = true +warn_redundant_casts = true +warn_unused_ignores = true - [tool.isort] - combine_as_imports = true - profile = "black" +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = [ + "src", + "tests", +] +norecursedirs = [ + ".*", + "*.egg*", + "build", + "dist", +] +addopts = [ + "--ignore=setup.py", + "--tb=native", + "--durations=16", + "--strict-markers", + "--doctest-modules", + "-vvv", +] - [tool.check-manifest] - ignore = [".nox", "build"] +[tool.zest-releaser] +tag-format = "v{version}" +python-file-with-version = "src/bmipy/_version.py" diff --git a/requirements.in b/requirements.in index 30fca5f..24ce15a 100644 --- a/requirements.in +++ b/requirements.in @@ -1,2 +1 @@ -click numpy diff --git a/requirements/requires.txt b/requirements/requires.txt index eeae57b..af47163 100644 --- a/requirements/requires.txt +++ b/requirements/requires.txt @@ -1,2 +1 @@ -click==8.1.7 numpy==1.26.3 diff --git a/requirements/testing.txt b/requirements/testing.txt index 42114c5..70c4cbd 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -1,3 +1,3 @@ coveralls==3.3.1 -pytest==7.4.2 pytest-cov==4.1.0 +pytest==7.4.2 diff --git a/setup.cfg b/setup.cfg index f701faa..c5c0cdf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,10 +4,3 @@ ignore = C901, E203, E266, E501, W503, B905 max-line-length = 88 max-complexity = 18 select = B,C,E,F,W,T4,B9 - -[zest.releaser] -tag-format = v{version} -python-file-with-version = src/bmipy/_version.py - -[coverage:run] -relative_files = True diff --git a/setup.py b/setup.py deleted file mode 100644 index df54f71..0000000 --- a/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -import os.path -import sys - -from setuptools import setup - -sys.path.append(os.path.dirname(__file__)) - -# See pyproject.toml for project metadata -setup( - name="bmipy", # need by GitHub dependency graph -) diff --git a/src/bmipy/__init__.py b/src/bmipy/__init__.py index 13dc3b3..27e652a 100644 --- a/src/bmipy/__init__.py +++ b/src/bmipy/__init__.py @@ -1,7 +1,7 @@ """The Basic Model Interface (BMI) for Python.""" from __future__ import annotations -from ._version import __version__ -from .bmi import Bmi +from bmipy._version import __version__ +from bmipy.bmi import Bmi __all__ = ["__version__", "Bmi"] diff --git a/src/bmipy/__main__.py b/src/bmipy/__main__.py new file mode 100644 index 0000000..19d6a0c --- /dev/null +++ b/src/bmipy/__main__.py @@ -0,0 +1,7 @@ +"""The bmipy-render command.""" +from __future__ import annotations + +from bmipy._cmd import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/bmipy/_cmd.py b/src/bmipy/_cmd.py new file mode 100644 index 0000000..0f859ab --- /dev/null +++ b/src/bmipy/_cmd.py @@ -0,0 +1,33 @@ +"""Command line interface that create template BMI implementations.""" +from __future__ import annotations + +import argparse +import keyword +import sys + +from bmipy._template import Template +from bmipy._version import __version__ + + +def main(args: tuple[str, ...] | None = None) -> int: + """Render a template BMI implementation in Python for class NAME.""" + parser = argparse.ArgumentParser() + parser.add_argument("--version", action="version", version=f"bmipy {__version__}") + parser.add_argument("name") + + parsed_args = parser.parse_args(args) + + if parsed_args.name.isidentifier() and not keyword.iskeyword(parsed_args.name): + print(Template(parsed_args.name).render()) + else: + print( + f"πŸ’₯ πŸ’” πŸ’₯ {parsed_args.name!r} is not a valid class name in Python", + file=sys.stderr, + ) + return 1 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/bmipy/_template.py b/src/bmipy/_template.py index b12907e..acb9981 100644 --- a/src/bmipy/_template.py +++ b/src/bmipy/_template.py @@ -19,7 +19,10 @@ def render(self) -> str: prefix = f"""\ from __future__ import annotations +from typing import Any + import numpy as np +from numpy.typing import NDArray from bmipy.bmi import Bmi @@ -52,7 +55,7 @@ def _render_func(self, name: str) -> str: return textwrap.indent(os.linesep.join(parts), " ") -def dedent_docstring(text: str | None, tabsize=4) -> str: +def dedent_docstring(text: str | None, tabsize: int = 4) -> str: """Dedent a docstring, ignoring indentation of the first line. Parameters diff --git a/src/bmipy/bmi.py b/src/bmipy/bmi.py index 807c67f..2158a5e 100644 --- a/src/bmipy/bmi.py +++ b/src/bmipy/bmi.py @@ -5,9 +5,12 @@ """ from __future__ import annotations -from abc import ABC, abstractmethod +from abc import ABC +from abc import abstractmethod +from typing import Any import numpy as np +from numpy.typing import NDArray class Bmi(ABC): @@ -249,8 +252,8 @@ def get_var_location(self, name: str) -> str: *face* A plane or surface enclosed by a set of edges. In a 2D - horizontal application one may consider the word β€œpolygon”, - but in the hierarchy of elements the word β€œface” is most common. + horizontal application one may consider the word "polygon", + but in the hierarchy of elements the word "face" is most common. Parameters ---------- @@ -335,7 +338,7 @@ def get_time_step(self) -> float: ... @abstractmethod - def get_value(self, name: str, dest: np.ndarray) -> np.ndarray: + def get_value(self, name: str, dest: NDArray[Any]) -> NDArray[Any]: """Get a copy of values of the given variable. This is a getter for the model, used to access the model's @@ -357,7 +360,7 @@ def get_value(self, name: str, dest: np.ndarray) -> np.ndarray: ... @abstractmethod - def get_value_ptr(self, name: str) -> np.ndarray: + def get_value_ptr(self, name: str) -> NDArray[Any]: """Get a reference to values of the given variable. This is a getter for the model, used to access the model's @@ -378,8 +381,8 @@ def get_value_ptr(self, name: str) -> np.ndarray: @abstractmethod def get_value_at_indices( - self, name: str, dest: np.ndarray, inds: np.ndarray - ) -> np.ndarray: + self, name: str, dest: NDArray[Any], inds: NDArray[np.int_] + ) -> NDArray[Any]: """Get values at particular indices. Parameters @@ -399,7 +402,7 @@ def get_value_at_indices( ... @abstractmethod - def set_value(self, name: str, src: np.ndarray) -> None: + def set_value(self, name: str, src: NDArray[Any]) -> None: """Specify a new value for a model variable. This is the setter for the model, used to change the model's @@ -418,7 +421,7 @@ def set_value(self, name: str, src: np.ndarray) -> None: @abstractmethod def set_value_at_indices( - self, name: str, inds: np.ndarray, src: np.ndarray + self, name: str, inds: NDArray[np.int_], src: NDArray[Any] ) -> None: """Specify a new value for a model variable at particular indices. @@ -484,7 +487,7 @@ def get_grid_type(self, grid: int) -> str: # Uniform rectilinear @abstractmethod - def get_grid_shape(self, grid: int, shape: np.ndarray) -> np.ndarray: + def get_grid_shape(self, grid: int, shape: NDArray[np.int_]) -> NDArray[np.int_]: """Get dimensions of the computational grid. Parameters @@ -502,7 +505,9 @@ def get_grid_shape(self, grid: int, shape: np.ndarray) -> np.ndarray: ... @abstractmethod - def get_grid_spacing(self, grid: int, spacing: np.ndarray) -> np.ndarray: + def get_grid_spacing( + self, grid: int, spacing: NDArray[np.float64] + ) -> NDArray[np.float64]: """Get distance between nodes of the computational grid. Parameters @@ -520,7 +525,9 @@ def get_grid_spacing(self, grid: int, spacing: np.ndarray) -> np.ndarray: ... @abstractmethod - def get_grid_origin(self, grid: int, origin: np.ndarray) -> np.ndarray: + def get_grid_origin( + self, grid: int, origin: NDArray[np.float64] + ) -> NDArray[np.float64]: """Get coordinates for the lower-left corner of the computational grid. Parameters @@ -541,7 +548,7 @@ def get_grid_origin(self, grid: int, origin: np.ndarray) -> np.ndarray: # Non-uniform rectilinear, curvilinear @abstractmethod - def get_grid_x(self, grid: int, x: np.ndarray) -> np.ndarray: + def get_grid_x(self, grid: int, x: NDArray[np.float64]) -> NDArray[np.float64]: """Get coordinates of grid nodes in the x direction. Parameters @@ -559,7 +566,7 @@ def get_grid_x(self, grid: int, x: np.ndarray) -> np.ndarray: ... @abstractmethod - def get_grid_y(self, grid: int, y: np.ndarray) -> np.ndarray: + def get_grid_y(self, grid: int, y: NDArray[np.float64]) -> NDArray[np.float64]: """Get coordinates of grid nodes in the y direction. Parameters @@ -577,7 +584,7 @@ def get_grid_y(self, grid: int, y: np.ndarray) -> np.ndarray: ... @abstractmethod - def get_grid_z(self, grid: int, z: np.ndarray) -> np.ndarray: + def get_grid_z(self, grid: int, z: NDArray[np.float64]) -> NDArray[np.float64]: """Get coordinates of grid nodes in the z direction. Parameters @@ -643,7 +650,9 @@ def get_grid_face_count(self, grid: int) -> int: ... @abstractmethod - def get_grid_edge_nodes(self, grid: int, edge_nodes: np.ndarray) -> np.ndarray: + def get_grid_edge_nodes( + self, grid: int, edge_nodes: NDArray[np.int_] + ) -> NDArray[np.int_]: """Get the edge-node connectivity. Parameters @@ -663,7 +672,9 @@ def get_grid_edge_nodes(self, grid: int, edge_nodes: np.ndarray) -> np.ndarray: ... @abstractmethod - def get_grid_face_edges(self, grid: int, face_edges: np.ndarray) -> np.ndarray: + def get_grid_face_edges( + self, grid: int, face_edges: NDArray[np.int_] + ) -> NDArray[np.int_]: """Get the face-edge connectivity. Parameters @@ -681,7 +692,9 @@ def get_grid_face_edges(self, grid: int, face_edges: np.ndarray) -> np.ndarray: ... @abstractmethod - def get_grid_face_nodes(self, grid: int, face_nodes: np.ndarray) -> np.ndarray: + def get_grid_face_nodes( + self, grid: int, face_nodes: NDArray[np.int_] + ) -> NDArray[np.int_]: """Get the face-node connectivity. Parameters @@ -702,8 +715,8 @@ def get_grid_face_nodes(self, grid: int, face_nodes: np.ndarray) -> np.ndarray: @abstractmethod def get_grid_nodes_per_face( - self, grid: int, nodes_per_face: np.ndarray - ) -> np.ndarray: + self, grid: int, nodes_per_face: NDArray[np.int_] + ) -> NDArray[np.int_]: """Get the number of nodes for each face. Parameters diff --git a/src/bmipy/cmd.py b/src/bmipy/cmd.py deleted file mode 100644 index 21837d7..0000000 --- a/src/bmipy/cmd.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Command line interface that create template BMI implementations.""" -from __future__ import annotations - -import keyword - -import click - -from bmipy._template import Template - - -@click.command() -@click.version_option() -@click.argument("name") -@click.pass_context -def main(ctx: click.Context, name: str): - """Render a template BMI implementation in Python for class NAME.""" - if name.isidentifier() and not keyword.iskeyword(name): - print(Template(name).render()) - else: - click.secho( - f"πŸ’₯ πŸ’” πŸ’₯ {name!r} is not a valid class name in Python", - err=True, - fg="red", - ) - ctx.exit(code=1) diff --git a/tests/cli_test.py b/tests/cli_test.py new file mode 100644 index 0000000..c4f4700 --- /dev/null +++ b/tests/cli_test.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import pytest +from bmipy._cmd import main + + +def test_cli_version(capsys): + try: + assert main(["--version"]) == 0 + except SystemExit: + pass + output = capsys.readouterr().out + + assert "bmipy" in output + + +def test_cli_help(capsys): + try: + assert main(["--help"]) == 0 + except SystemExit: + pass + output = capsys.readouterr().out + + assert "help" in output + + +def test_cli_default(capsys): + assert main(["MyUniqueBmi"]) == 0 + globs = {} + exec(capsys.readouterr().out, globs) + assert "MyUniqueBmi" in globs + + +def test_cli_wraps_lines(capsys): + assert main(["MyBmi"]) == 0 + output = capsys.readouterr().out + assert max(len(line) for line in output.splitlines()) <= 88 + + +def test_cli_with_hints(capsys): + assert main(["MyBmiWithHints"]) == 0 + output = capsys.readouterr().out + assert "->" in output + + +@pytest.mark.parametrize("bad_name", ["True", "0Bmi"]) +def test_cli_with_bad_class_name(capsys, bad_name): + assert main([bad_name]) != 0 diff --git a/tests/test_template.py b/tests/template_test.py similarity index 94% rename from tests/test_template.py rename to tests/template_test.py index b4c7502..731df56 100644 --- a/tests/test_template.py +++ b/tests/template_test.py @@ -1,6 +1,8 @@ -import pytest +from __future__ import annotations -from bmipy._template import dedent_docstring, render_function_signature +import pytest +from bmipy._template import dedent_docstring +from bmipy._template import render_function_signature @pytest.mark.parametrize( diff --git a/tests/test_bmipy.py b/tests/test_bmipy.py deleted file mode 100644 index d4bad78..0000000 --- a/tests/test_bmipy.py +++ /dev/null @@ -1,143 +0,0 @@ -import pytest - -from bmipy import Bmi - - -class EmptyBmi(Bmi): - def __init__(self): - pass - - def initialize(self, config_file): - pass - - def update(self): - pass - - def update_until(self, then): - pass - - def finalize(self): - pass - - def get_var_type(self, var_name): - pass - - def get_var_units(self, var_name): - pass - - def get_var_nbytes(self, var_name): - pass - - def get_var_itemsize(self, name): - pass - - def get_var_location(self, name): - pass - - def get_var_grid(self, var_name): - pass - - def get_grid_rank(self, grid_id): - pass - - def get_grid_size(self, grid_id): - pass - - def get_value_ptr(self, var_name): - pass - - def get_value(self, var_name): - pass - - def get_value_at_indices(self, var_name, indices): - pass - - def set_value(self, var_name, src): - pass - - def set_value_at_indices(self, var_name, src, indices): - pass - - def get_component_name(self): - pass - - def get_input_item_count(self): - pass - - def get_output_item_count(self): - pass - - def get_input_var_names(self): - pass - - def get_output_var_names(self): - pass - - def get_grid_shape(self, grid_id): - pass - - def get_grid_spacing(self, grid_id): - pass - - def get_grid_origin(self, grid_id): - pass - - def get_grid_type(self, grid_id): - pass - - def get_start_time(self): - pass - - def get_end_time(self): - pass - - def get_current_time(self): - pass - - def get_time_step(self): - pass - - def get_time_units(self): - pass - - def get_grid_edge_count(self, grid): - pass - - def get_grid_edge_nodes(self, grid, edge_nodes): - pass - - def get_grid_face_count(self, grid): - pass - - def get_grid_face_nodes(self, grid, face_nodes): - pass - - def get_grid_face_edges(self, grid, face_edges): - pass - - def get_grid_node_count(self, grid): - pass - - def get_grid_nodes_per_face(self, grid, nodes_per_face): - pass - - def get_grid_x(self, grid, x): - pass - - def get_grid_y(self, grid, y): - pass - - def get_grid_z(self, grid, z): - pass - - -def test_bmi_not_implemented(): - class MyBmi(Bmi): - pass - - with pytest.raises(TypeError): - Bmi() - - -def test_bmi_implemented(): - assert isinstance(EmptyBmi(), Bmi) diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index f31ef22..0000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,62 +0,0 @@ -import importlib -import sys - -import pytest -from click.testing import CliRunner - -from bmipy.cmd import main - - -def test_cli_version(): - runner = CliRunner() - result = runner.invoke(main, ["--version"]) - assert result.exit_code == 0 - assert "version" in result.output - - -def test_cli_help(): - runner = CliRunner() - result = runner.invoke(main, ["--help"]) - assert result.exit_code == 0 - assert "help" in result.output - - -@pytest.mark.skipif( - sys.platform == "win32", reason="See https://github.com/csdms/bmi-python/issues/10" -) -def test_cli_default(tmpdir): - runner = CliRunner() - with tmpdir.as_cwd(): - result = runner.invoke(main, ["MyBmi"]) - assert result.exit_code == 0 - with open("mybmi.py", "w") as fp: - fp.write(result.output) - sys.path.append(".") - mod = importlib.import_module("mybmi") - assert "MyBmi" in mod.__dict__ - - -@pytest.mark.skipif( - sys.platform == "win32", reason="See https://github.com/csdms/bmi-python/issues/10" -) -def test_cli_wraps_lines(tmpdir): - runner = CliRunner() - with tmpdir.as_cwd(): - result = runner.invoke(main, ["MyBmi"]) - assert result.exit_code == 0 - assert max(len(line) for line in result.output.splitlines()) <= 88 - - -def test_cli_with_hints(tmpdir): - runner = CliRunner() - with tmpdir.as_cwd(): - result = runner.invoke(main, ["MyBmiWithHints"]) - assert result.exit_code == 0 - assert "->" in result.output - - -@pytest.mark.parametrize("bad_name", ["True", "0Bmi"]) -def test_cli_with_bad_class_name(tmpdir, bad_name): - runner = CliRunner() - result = runner.invoke(main, [bad_name]) - assert result.exit_code == 1