From b8c71ff5366ef310c075e0497dccb4c1e24fe3e9 Mon Sep 17 00:00:00 2001 From: Hofer-Julian <30049909+Hofer-Julian@users.noreply.github.com> Date: Thu, 23 Mar 2023 21:28:55 +0100 Subject: [PATCH] Create package `Ribasim API` (#130) This PR creates the package similar to https://github.com/MODFLOW-USGS/modflowapi - This allows to expose functionality that doesn't fit into xmipy - Allows us to override xmipy functionality (in our case `get_constant_int` but ideally we would get rid of that mid-term) The PR also moves shared config like `environment.yml` and `ruff.toml` into the root of the repo --- .github/workflows/docs.yml | 5 +-- .github/workflows/python_lint.yml | 10 ++--- .github/workflows/python_tests.yml | 8 ++-- .vscode/settings_template.json | 6 +-- docs/python/developer.qmd | 6 --- python/environment.yml => environment.yml | 0 python/{ => ribasim}/LICENSE | 0 python/{ => ribasim}/README.md | 0 python/ribasim/__init__.py | 16 -------- python/{ => ribasim}/pyproject.toml | 7 +--- python/ribasim/ribasim/__init__.py | 27 +++++++++++++ python/ribasim/{ => ribasim}/basin.py | 0 python/ribasim/{ => ribasim}/edge.py | 0 .../ribasim/{ => ribasim}/fractional_flow.py | 0 python/ribasim/{ => ribasim}/input_base.py | 0 python/ribasim/{ => ribasim}/level_control.py | 0 .../{ => ribasim}/linear_level_connection.py | 0 python/ribasim/{ => ribasim}/model.py | 0 python/ribasim/{ => ribasim}/node.py | 0 python/ribasim/{ => ribasim}/pump.py | 0 .../{ => ribasim}/tabulated_rating_curve.py | 0 python/ribasim/{ => ribasim}/types.py | 0 python/ribasim/{ => ribasim}/utils.py | 0 python/{ => ribasim}/tests/test_edge.py | 1 - python/ribasim_api/LICENSE | 21 ++++++++++ python/ribasim_api/pyproject.toml | 40 +++++++++++++++++++ python/ribasim_api/ribasim_api/__init__.py | 5 +++ python/ribasim_api/ribasim_api/ribasim_api.py | 29 ++++++++++++++ .../ribasim_api}/tests/conftest.py | 18 ++++----- .../ribasim_api}/tests/test_bmi.py | 3 -- ruff.toml | 3 ++ 31 files changed, 145 insertions(+), 60 deletions(-) rename python/environment.yml => environment.yml (100%) rename python/{ => ribasim}/LICENSE (100%) rename python/{ => ribasim}/README.md (100%) delete mode 100644 python/ribasim/__init__.py rename python/{ => ribasim}/pyproject.toml (89%) create mode 100644 python/ribasim/ribasim/__init__.py rename python/ribasim/{ => ribasim}/basin.py (100%) rename python/ribasim/{ => ribasim}/edge.py (100%) rename python/ribasim/{ => ribasim}/fractional_flow.py (100%) rename python/ribasim/{ => ribasim}/input_base.py (100%) rename python/ribasim/{ => ribasim}/level_control.py (100%) rename python/ribasim/{ => ribasim}/linear_level_connection.py (100%) rename python/ribasim/{ => ribasim}/model.py (100%) rename python/ribasim/{ => ribasim}/node.py (100%) rename python/ribasim/{ => ribasim}/pump.py (100%) rename python/ribasim/{ => ribasim}/tabulated_rating_curve.py (100%) rename python/ribasim/{ => ribasim}/types.py (100%) rename python/ribasim/{ => ribasim}/utils.py (100%) rename python/{ => ribasim}/tests/test_edge.py (99%) create mode 100644 python/ribasim_api/LICENSE create mode 100644 python/ribasim_api/pyproject.toml create mode 100644 python/ribasim_api/ribasim_api/__init__.py create mode 100644 python/ribasim_api/ribasim_api/ribasim_api.py rename {build/libribasim => python/ribasim_api}/tests/conftest.py (54%) rename {build/libribasim => python/ribasim_api}/tests/test_bmi.py (89%) create mode 100644 ruff.toml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a1d6675ae..7464c884c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -49,11 +49,10 @@ jobs: python_version: ${{ matrix.python_version }} cache-downloads: true cache-env: true - environment-file: ./python/environment.yml + environment-file: ./environment.yml - name: Install ribasim-python - working-directory: python - run: pip install -e . + run: pip install -e python/ribasim - name: Set up Quarto uses: quarto-dev/quarto-actions/setup@v2 diff --git a/.github/workflows/python_lint.yml b/.github/workflows/python_lint.yml index bda1aff80..6f43aa4d9 100644 --- a/.github/workflows/python_lint.yml +++ b/.github/workflows/python_lint.yml @@ -32,13 +32,13 @@ jobs: python_version: ${{ matrix.python_version }} cache-downloads: true cache-env: true - environment-file: ./python/environment.yml + environment-file: ./environment.yml - name: Run black - working-directory: python - run: black --check . + run: | + black --check python # Include `--format=github` to enable automatic inline annotations. - name: Run ruff - working-directory: python - run: ruff check --format=github . + run: | + ruff check --format=github python diff --git a/.github/workflows/python_tests.yml b/.github/workflows/python_tests.yml index 621a9f64c..25132268f 100644 --- a/.github/workflows/python_tests.yml +++ b/.github/workflows/python_tests.yml @@ -35,12 +35,10 @@ jobs: python_version: ${{ matrix.python_version }} cache-downloads: true cache-env: true - environment-file: ./python/environment.yml + environment-file: ./environment.yml - name: Install ribasim - working-directory: python - run: pip install -e . + run: pip install -e python/ribasim - name: Run tests - working-directory: python - run: pytest tests + run: pytest python/ribasim/tests diff --git a/.vscode/settings_template.json b/.vscode/settings_template.json index c09555604..d92606db8 100644 --- a/.vscode/settings_template.json +++ b/.vscode/settings_template.json @@ -11,9 +11,5 @@ }, "python.formatting.provider": "black", "python.linting.mypyEnabled": true, - "python.linting.enabled": true, - "ruff.args": [ - "--config", - "python/pyproject.toml" - ] + "python.linting.enabled": true } diff --git a/docs/python/developer.qmd b/docs/python/developer.qmd index 5c6f6b098..40e66a720 100644 --- a/docs/python/developer.qmd +++ b/docs/python/developer.qmd @@ -24,12 +24,6 @@ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned ## Creating (or updating) the environment -- Change your current working directory to `python` - -``` -cd python -``` - - Create (or update) the environment by executing the following in your terminal: ``` diff --git a/python/environment.yml b/environment.yml similarity index 100% rename from python/environment.yml rename to environment.yml diff --git a/python/LICENSE b/python/ribasim/LICENSE similarity index 100% rename from python/LICENSE rename to python/ribasim/LICENSE diff --git a/python/README.md b/python/ribasim/README.md similarity index 100% rename from python/README.md rename to python/ribasim/README.md diff --git a/python/ribasim/__init__.py b/python/ribasim/__init__.py deleted file mode 100644 index c24387959..000000000 --- a/python/ribasim/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -__version__ = "0.1.1" - - -from ribasim import utils as utils -from ribasim.basin import Basin as Basin -from ribasim.edge import Edge as Edge -from ribasim.fractional_flow import FractionalFlow as FractionalFlow -from ribasim.level_control import LevelControl as LevelControl -from ribasim.linear_level_connection import ( - LinearLevelConnection as LinearLevelConnection, -) -from ribasim.model import Model as Model -from ribasim.model import Solver as Solver -from ribasim.node import Node as Node -from ribasim.pump import Pump as Pump -from ribasim.tabulated_rating_curve import TabulatedRatingCurve as TabulatedRatingCurve diff --git a/python/pyproject.toml b/python/ribasim/pyproject.toml similarity index 89% rename from python/pyproject.toml rename to python/ribasim/pyproject.toml index 93a10d3ca..1ffc6ceb3 100644 --- a/python/pyproject.toml +++ b/python/ribasim/pyproject.toml @@ -9,6 +9,7 @@ readme = "README.md" authors = [ { name = "Huite Bootsma", email = "Huite.Bootsma@deltares.nl" }, { name = "Julian Hofer", email = "Julian.Hofer@deltares.nl" }, + { name = "Martijn Visser", email = "Martijn.Visser@deltares.nl" }, ] license = { text = "MIT" } classifiers = [ @@ -32,7 +33,6 @@ dynamic = ["version"] [project.optional-dependencies] tests = ["pytest", "pytest-cov"] -lint = ["black", "ruff", "mypy"] [tool.setuptools] zip-safe = true @@ -49,8 +49,3 @@ include = ["ribasim"] [project.urls] Documentation = "https://deltares.github.io/Ribasim" Source = "https://github.com/Deltares/Ribasim" - -[tool.ruff] -select = ["E", "F", "NPY", "PD", "C4", "I"] -ignore = ["E501", "PD901"] -fixable = ["I"] diff --git a/python/ribasim/ribasim/__init__.py b/python/ribasim/ribasim/__init__.py new file mode 100644 index 000000000..414e27c20 --- /dev/null +++ b/python/ribasim/ribasim/__init__.py @@ -0,0 +1,27 @@ +__version__ = "0.1.1" + + +from ribasim import utils +from ribasim.basin import Basin +from ribasim.edge import Edge +from ribasim.fractional_flow import FractionalFlow +from ribasim.level_control import LevelControl +from ribasim.linear_level_connection import LinearLevelConnection +from ribasim.model import Model, Solver +from ribasim.node import Node +from ribasim.pump import Pump +from ribasim.tabulated_rating_curve import TabulatedRatingCurve + +__all__ = [ + "utils", + "Basin", + "Edge", + "FractionalFlow", + "LevelControl", + "LinearLevelConnection", + "Model", + "Solver", + "Node", + "Pump", + "TabulatedRatingCurve", +] diff --git a/python/ribasim/basin.py b/python/ribasim/ribasim/basin.py similarity index 100% rename from python/ribasim/basin.py rename to python/ribasim/ribasim/basin.py diff --git a/python/ribasim/edge.py b/python/ribasim/ribasim/edge.py similarity index 100% rename from python/ribasim/edge.py rename to python/ribasim/ribasim/edge.py diff --git a/python/ribasim/fractional_flow.py b/python/ribasim/ribasim/fractional_flow.py similarity index 100% rename from python/ribasim/fractional_flow.py rename to python/ribasim/ribasim/fractional_flow.py diff --git a/python/ribasim/input_base.py b/python/ribasim/ribasim/input_base.py similarity index 100% rename from python/ribasim/input_base.py rename to python/ribasim/ribasim/input_base.py diff --git a/python/ribasim/level_control.py b/python/ribasim/ribasim/level_control.py similarity index 100% rename from python/ribasim/level_control.py rename to python/ribasim/ribasim/level_control.py diff --git a/python/ribasim/linear_level_connection.py b/python/ribasim/ribasim/linear_level_connection.py similarity index 100% rename from python/ribasim/linear_level_connection.py rename to python/ribasim/ribasim/linear_level_connection.py diff --git a/python/ribasim/model.py b/python/ribasim/ribasim/model.py similarity index 100% rename from python/ribasim/model.py rename to python/ribasim/ribasim/model.py diff --git a/python/ribasim/node.py b/python/ribasim/ribasim/node.py similarity index 100% rename from python/ribasim/node.py rename to python/ribasim/ribasim/node.py diff --git a/python/ribasim/pump.py b/python/ribasim/ribasim/pump.py similarity index 100% rename from python/ribasim/pump.py rename to python/ribasim/ribasim/pump.py diff --git a/python/ribasim/tabulated_rating_curve.py b/python/ribasim/ribasim/tabulated_rating_curve.py similarity index 100% rename from python/ribasim/tabulated_rating_curve.py rename to python/ribasim/ribasim/tabulated_rating_curve.py diff --git a/python/ribasim/types.py b/python/ribasim/ribasim/types.py similarity index 100% rename from python/ribasim/types.py rename to python/ribasim/ribasim/types.py diff --git a/python/ribasim/utils.py b/python/ribasim/ribasim/utils.py similarity index 100% rename from python/ribasim/utils.py rename to python/ribasim/ribasim/utils.py diff --git a/python/tests/test_edge.py b/python/ribasim/tests/test_edge.py similarity index 99% rename from python/tests/test_edge.py rename to python/ribasim/tests/test_edge.py index c28e10c49..7466514d8 100644 --- a/python/tests/test_edge.py +++ b/python/ribasim/tests/test_edge.py @@ -2,7 +2,6 @@ import pytest import shapely.geometry as sg from pydantic import ValidationError - from ribasim.edge import Edge diff --git a/python/ribasim_api/LICENSE b/python/ribasim_api/LICENSE new file mode 100644 index 000000000..42a9a5aa1 --- /dev/null +++ b/python/ribasim_api/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Ribasim developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/python/ribasim_api/pyproject.toml b/python/ribasim_api/pyproject.toml new file mode 100644 index 000000000..a67d6fce2 --- /dev/null +++ b/python/ribasim_api/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[project] +name = "ribasim_api" +description = "Python bindings for libribasim" +readme = "README.md" +authors = [ + { name = "Julian Hofer", email = "Julian.Hofer@deltares.nl" }, +] +license = { text = "MIT" } +classifiers = [ + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Hydrology", +] +requires-python = ">=3.10" +dependencies = [ + "xmipy" +] +dynamic = ["version"] + +[project.optional-dependencies] +tests = ["pytest", "pytest-cov"] + +[tool.setuptools] +zip-safe = true + +[tool.setuptools.dynamic] +version = { attr = "ribasim_api.__version__" } + +[tool.setuptools.packages.find] +include = ["ribasim_api"] + +[tool.setuptools.package-data] +"ribasim" = ["py.typed"] + +[project.urls] +Documentation = "https://deltares.github.io/Ribasim" +Source = "https://github.com/Deltares/Ribasim" diff --git a/python/ribasim_api/ribasim_api/__init__.py b/python/ribasim_api/ribasim_api/__init__.py new file mode 100644 index 000000000..d508b33d1 --- /dev/null +++ b/python/ribasim_api/ribasim_api/__init__.py @@ -0,0 +1,5 @@ +__version__ = "0.1.1" + +from ribasim_api.ribasim_api import RibasimApi + +__all__ = ["RibasimApi"] diff --git a/python/ribasim_api/ribasim_api/ribasim_api.py b/python/ribasim_api/ribasim_api/ribasim_api.py new file mode 100644 index 000000000..6914deae8 --- /dev/null +++ b/python/ribasim_api/ribasim_api/ribasim_api.py @@ -0,0 +1,29 @@ +# %% +from ctypes import byref, c_int, create_string_buffer + +from xmipy import XmiWrapper + + +class RibasimApi(XmiWrapper): + def get_constant_int(self, name: str) -> int: + match name: + case "BMI_LENVARTYPE": + return 51 + case "BMI_LENGRIDTYPE": + return 17 + case "BMI_LENVARADDRESS": + return 68 + case "BMI_LENCOMPONENTNAME": + return 256 + case "BMI_LENVERSION": + return 256 + case "BMI_LENERRMESSAGE": + return 1025 + raise ValueError(f"{name} does not map to an integer exposed by Ribasim") + + def init_julia(self) -> None: + argument = create_string_buffer(0) + self.lib.init_julia(c_int(0), byref(argument)) + + def shutdown_julia(self) -> None: + self.lib.shutdown_julia(c_int(0)) diff --git a/build/libribasim/tests/conftest.py b/python/ribasim_api/tests/conftest.py similarity index 54% rename from build/libribasim/tests/conftest.py rename to python/ribasim_api/tests/conftest.py index 39044f0cd..502ef2f9a 100644 --- a/build/libribasim/tests/conftest.py +++ b/python/ribasim_api/tests/conftest.py @@ -1,14 +1,13 @@ -from ctypes import byref, c_int, create_string_buffer from pathlib import Path import pytest -from xmipy import XmiWrapper +from ribasim_api import RibasimApi @pytest.fixture(scope="session") def libribasim_paths() -> tuple[Path, Path]: - test_dir = Path(__file__).parent.resolve() - lib_folder = test_dir.parents[1] / "create_binaries" / "libribasim" / "bin" + repo_root = Path(__file__).parents[2].resolve() + lib_folder = repo_root / "build" / "create_binaries" / "libribasim" / "bin" lib_path = lib_folder / "libribasim" return lib_path, lib_folder @@ -16,20 +15,19 @@ def libribasim_paths() -> tuple[Path, Path]: @pytest.fixture(scope="session", autouse=True) def load_julia(libribasim_paths) -> None: lib_path, lib_folder = libribasim_paths - libribasim = XmiWrapper(lib_path, lib_folder) - argument = create_string_buffer(0) - libribasim.lib.init_julia(c_int(0), byref(argument)) + libribasim = RibasimApi(lib_path, lib_folder) + libribasim.init_julia() @pytest.fixture -def ribasim_basic(libribasim_paths, request) -> tuple[XmiWrapper, str]: +def ribasim_basic(libribasim_paths, request) -> tuple[RibasimApi, str]: lib_path, lib_folder = libribasim_paths - libribasim = XmiWrapper(lib_path, lib_folder) + libribasim = RibasimApi(lib_path, lib_folder) # If initialized, call finalize() at end of use request.addfinalizer(libribasim.__del__) - repo_root = Path(__file__).parents[3].resolve() + repo_root = Path(__file__).parents[2].resolve() config_file = str(repo_root / "data" / "basic" / "basic.toml") return libribasim, config_file diff --git a/build/libribasim/tests/test_bmi.py b/python/ribasim_api/tests/test_bmi.py similarity index 89% rename from build/libribasim/tests/test_bmi.py rename to python/ribasim_api/tests/test_bmi.py index d4ad07b17..099a1db75 100644 --- a/build/libribasim/tests/test_bmi.py +++ b/python/ribasim_api/tests/test_bmi.py @@ -1,6 +1,3 @@ -from xmipy import XmiWrapper - - def test_initialize(ribasim_basic): libribasim, config_file = ribasim_basic libribasim.initialize(config_file) diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..8deedfaaf --- /dev/null +++ b/ruff.toml @@ -0,0 +1,3 @@ +select = ["E", "F", "NPY", "PD", "C4", "I"] +ignore = ["E501", "PD901"] +fixable = ["I"]