Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
Changelog
=========

v0.18.1 (2025-04-16)
--------------------

New features
^^^^^^^^^^^^
* RavenPy no longer requires `raven-hydro` to be installed. The Raven model executable can now be provided by explicitly setting the `RAVENPY_RAVEN_BINARY_PATH` environment variable. (PR #486).

Internal changes
^^^^^^^^^^^^^^^^
* `pydap` has been pinned below v3.5.5 temporarily until `xarray` offers support for it. (PR #486).

v0.18.0 (2025-04-03)
--------------------

Expand Down
8 changes: 7 additions & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,16 @@ Then, from your python environment, run:

.. code-block:: console

python -m pip install ravenpy[gis]
python -m pip install ravenpy[gis,raven-hydro]

If desired, the core functions of `RavenPy` can be installed without its GIS functionalities as well. This implementation of RavenPy is much lighter on dependencies and can be installed easily with `pip`, without the need for `conda` or `virtualenv`.

.. code-block:: console

python -m pip install ravenpy[raven-hydro]

Finally, if you wish to provide your own `Raven` binary, you can install `RavenPy` without installing the `raven-hydro` package:

.. code-block:: console

python -m pip install ravenpy
Expand Down
2 changes: 1 addition & 1 deletion environment-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies:
- pint >=0.24.4
- platformdirs >=4.3.6
- pydantic >=2.0
- pydap >=3.4.0 # Note: As of 2025-03-18 (v3.5.4) does not support Python 3.13
- pydap >=3.4.0,<3.5.5 # pydap 3.5.5 is not currently supported by `xarray` (v2025.3.1)
- pymetalink >=6.5.2
- pymbolic >=2024.2
- pyproj >=3.3.0
Expand Down
9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ dependencies = [
"pint >=0.24.4",
"platformdirs >=4.3.6",
"pydantic >=2.0",
"pydap >=3.4.0", # Note: As of 2025-03-18 (v3.5.4) does not support Python 3.13
"pydap >=3.4.0,<3.5.5", # pydap 3.5.5 is not currently supported by `xarray` (v2025.3.1)
"pymbolic >=2024.2",
"raven-hydro >=0.4.0,<1.0",
"scipy >=1.11.0",
"spotpy >=1.6.1",
"statsmodels >=0.14.2",
Expand Down Expand Up @@ -137,10 +136,14 @@ gis = [
"setuptools >=71.0",
"shapely >=2.0"
]
raven-hydro = [
"raven-hydro >=0.4.0,<1.0"
]
all = [
"ravenpy[dev]",
"ravenpy[docs]",
"ravenpy[gis]"
"ravenpy[gis]",
"ravenpy[raven-hydro]"
]

[project.scripts]
Expand Down
3 changes: 1 addition & 2 deletions src/ravenpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@
# SOFTWARE.
###################################################################################

from ._raven import RAVEN_EXEC_PATH, __raven_version__ # noqa: F401
from .ravenpy import Emulator, EnsembleReader, OutputReader, RavenWarning, run

__all__ = ["Emulator", "EnsembleReader", "OutputReader", "RavenWarning", "run"]

__author__ = """David Huard"""
__email__ = "huard.david@ouranos.ca"
__version__ = "0.18.0"
16 changes: 16 additions & 0 deletions src/ravenpy/_raven.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Configurations for raven-hydro."""

import os
import shutil

RAVEN_EXEC_PATH = os.getenv("RAVENPY_RAVEN_BINARY_PATH") or shutil.which("raven")

if not RAVEN_EXEC_PATH:
raise RuntimeError(
"Could not find raven binary in PATH and RAVENPY_RAVEN_BINARY_PATH env variable is not set."
)

try:
from raven_hydro import __raven_version__
except ImportError:
__raven_version__ = "0.0.0"
4 changes: 2 additions & 2 deletions src/ravenpy/config/defaults.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from raven_hydro import __raven_version__
from ravenpy import __raven_version__

units = {
"PRECIP": "mm/d",
Expand Down Expand Up @@ -65,5 +65,5 @@ def default_nc_attrs():
return {
"history": f"Created on {now} by Raven {version}",
"references": "Craig, J.R., and the Raven Development Team, Raven user's and developer's manual "
f"(Version {version}), URL: https://raven.uwaterloo.ca/ (2025).",
f"(Version {version}), URL: https://raven.uwaterloo.ca/ ({dt.datetime.today().year}).",
}
11 changes: 7 additions & 4 deletions src/ravenpy/config/rvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@

import cftime
from pydantic import ConfigDict, Field, ValidationInfo, field_validator
from raven_hydro import __raven_version__

from ..config import commands as rc
from ..config import options as o
from ..config import processes as rp
from .base import RV, Sym, optfield, parse_symbolic

try:
from raven_hydro import __raven_version__
except ImportError:
__raven_version__ = "0.0.0"


"""
Generic Raven model configuration.

Expand Down Expand Up @@ -268,12 +273,10 @@ def header(rv):

import ravenpy

version = __raven_version__

return dedent(
f"""
###########################################################################################################
:FileType {rv.upper()} Raven {version}
:FileType {rv.upper()} Raven {__raven_version__}
:WrittenBy RavenPy {ravenpy.__version__} based on setups provided by James Craig and Juliane Mai
:CreationDate {dt.datetime.now().isoformat(timespec="seconds")}
#----------------------------------------------------------------------------------------------------------
Expand Down
20 changes: 8 additions & 12 deletions src/ravenpy/ravenpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

import xarray as xr

from ravenpy import RAVEN_EXEC_PATH

from .config import parsers
from .config.rvs import Config

RAVEN_EXEC_PATH = os.getenv("RAVENPY_RAVEN_BINARY_PATH") or shutil.which("raven")


class Emulator:
def __init__(
Expand Down Expand Up @@ -278,18 +278,14 @@ def run(
overwrite : bool
If True, overwrite existing files.
verbose : bool
If True, always display Raven warnings. If False, warnings will only be printed if an error occurs.
If True, always display Raven warnings.
If False, warnings will only be printed if an error occurs.

Returns
-------
Path
Path to model outputs.
The path to the model outputs.
"""
if not RAVEN_EXEC_PATH:
raise RuntimeError(
"Could not find raven binary in PATH, and RAVENPY_RAVEN_BINARY_PATH env variable is not set"
)

# Confirm configdir exists
configdir = Path(configdir).absolute()
if not configdir.exists():
Expand Down Expand Up @@ -318,7 +314,7 @@ def run(
)

stdout, stderr = process.communicate(input="\n")
returncode = process.wait()
return_code = process.wait()

# Deal with errors and warnings
messages = parsers.parse_raven_messages(outputdir / "Raven_errors.txt")
Expand All @@ -332,8 +328,8 @@ def run(
"\n".join([f"Config directory: {configdir}"] + messages["ERROR"])
)

if returncode != 0:
raise OSError(f"Raven Error (code: {returncode}): \n{stdout}\n{stderr}")
if return_code != 0:
raise OSError(f"Raven Error (code: {return_code}): \n{stdout}\n{stderr}")

return outputdir

Expand Down
71 changes: 71 additions & 0 deletions tests/test_missing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import os
import shutil
import sys

import pytest


def filter_raven():
# Find the real conda-installed tool path
real_tool = shutil.which("raven")
assert real_tool is not None, "raven must be installed for this test"

# Get the directory the conda tool is in
conda_tool_dir = os.path.dirname(real_tool)

# Create a new PATH that includes everything *except* the conda env's tool directory
filtered_path = os.pathsep.join(
[
p
for p in os.environ["PATH"].split(os.pathsep)
if os.path.abspath(p) != os.path.abspath(conda_tool_dir)
]
)

return filtered_path


@pytest.fixture
def hide_module(monkeypatch):
def _hide(name):
monkeypatch.setitem(sys.modules, name, None)

return _hide


class TestMissing:

def test_missing_raven_binary(self, monkeypatch, tmpdir, hide_module):
"""Test for behaviour when binary is missing from the system path."""

# Set up a temporary directory to simulate the absence of the raven binary
filtered_path = filter_raven()
monkeypatch.setenv("PATH", f"{tmpdir}{os.pathsep}{filtered_path}")

# Hide the raven_hydro module
hide_module("raven_hydro")
hide_module("raven_hydro._version")
hide_module("raven_hydro.libraven")

# Force the raven modules to be reloaded
del sys.modules["ravenpy"]
del sys.modules["ravenpy._raven"]
del sys.modules["ravenpy.config.defaults"]

# Now the tool should be "missing"
assert shutil.which("raven") is None

# Loading the module should raise a RuntimeError
with pytest.raises(RuntimeError):
import ravenpy # noqa: F401

# Check that setting the RAVENPY_RAVEN_BINARY_PATH environment variable works
monkeypatch.setenv("RAVENPY_RAVEN_BINARY_PATH", f"some/path/to/raven")
import ravenpy

assert ravenpy.RAVEN_EXEC_PATH == "some/path/to/raven"

# Check that the raven_hydro library is not imported
from ravenpy.config.defaults import __raven_version__

assert __raven_version__ == "0.0.0"
4 changes: 2 additions & 2 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def test_nc_specs_bad(bad_netcdf):
@pytest.mark.online
def test_dap_specs():
# Link to THREDDS Data Server netCDF testdata
TDS = "https://pavics.ouranos.ca/twitcher/ows/proxy/thredds/dodsC/birdhouse/testdata/raven"
fn = f"{TDS}/raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily.nc"
tds = "https://pavics.ouranos.ca/twitcher/ows/proxy/thredds/dodsC/birdhouse/testdata/raven"
fn = f"{tds}/raven-gr4j-cemaneige/Salmon-River-Near-Prince-George_meteo_daily.nc"

attrs = nc_specs(fn, "PRECIP", station_idx=1, alt_names=("rain",), engine="pydap")
assert "units" in attrs
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ passenv =
extras =
dev
gis
raven-hydro
download = true
install_command =
python -m pip install --no-user {opts} {packages}
Expand Down