Skip to content

Commit

Permalink
Auto install test dependencies (#245)
Browse files Browse the repository at this point in the history
* Pixi task to download metaswap and imod collector

* Add .env generator for test dependencies

* Simplify README for contributing

* Add instruction to install svn

* Don't use regular expression after globbing

* Move imod_collector outside of .pixi folder

* Replace backslashes with forward slashes in .env

Makes pixi run tests work again

* Rename functions in scripts
  • Loading branch information
deltamarnix authored Feb 5, 2024
1 parent 608a945 commit bedab9b
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,6 @@ dmypy.json
# Tests
tests/temp
report.xml

.pixi
.imod_collector
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"charliermarsh.ruff",
"njpwerner.autodocstring",
"tamasfe.even-better-toml",
"davidanson.vscode-markdownlint"
"davidanson.vscode-markdownlint",
"samuelcolvin.jinjahtml"
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@
}
},
"mypy-type-checker.importStrategy": "fromEnvironment",
"files.associations": {
".env.jinja": "jinja-properties"
}
}
64 changes: 25 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,56 +29,42 @@ Deltares colleagues can find the issue tracker at [Jira](https://issuetracker.de

In order to develop on `imod_coupler` locally, please follow the following steps:

- Download and install [pixi](https://prefix.dev/docs/pixi/overview).

- Create an access token at the [TeamCity build server](https://dpcbuild.deltares.nl/profile.html?item=accessTokens#).
Choose permission scope: `<Same as current user>`.
- Store the token in your local user environment as `TEAMCITY_TOKEN`.
This token will be used to download artifacts from Teamcity, make sure to store it well.
- Download and install [pixi](https://pixi.sh).
- Download and install [svn](https://tortoisesvn.net/downloads.html).
Make sure to install the svn command line tools as well.
- Download the Git repository of `imod_coupler` and navigate to the root of the project.
- Create the environment by executing the following in your terminal:

```sh
pixi run install
```

- With your Deltares credentials download
- the [latest imod_collector](https://dpcbuild.deltares.nl/buildConfiguration/iMOD6_IMOD6collectorDaily_ReleaseX64?branch=%3Cdefault%3E&mode=builds), and
- the [regression imod_collector](https://dpcbuild.deltares.nl/buildConfiguration/iMOD6_IMOD6collectorDaily_ReleaseX64?branch=%3Cdefault%3E&mode=builds&tag=regression).
```sh
pixi run install
```

- Unpack the two zip files in a path of your choice and name the latest `imod_collector_devel` and the regression `imod_collector_regression`.
- Install the test dependencies by executing the following in your terminal.
It automatically downloads the [latest imod_collector](https://dpcbuild.deltares.nl/buildConfiguration/iMOD6_IMOD6collectorDaily_ReleaseX64?branch=%3Cdefault%3E&mode=builds) and [regression imod_collector](https://dpcbuild.deltares.nl/buildConfiguration/iMOD6_IMOD6collectorDaily_ReleaseX64?branch=%3Cdefault%3E&mode=builds&tag=regression) from the build server.
It downloads the [MetaSWAP lookup table](https://repos.deltares.nl/repos/DSCTestbench/trunk/cases/e150_metaswap/f00_common/c00_common/LHM2016_v01vrz).
It also generates a `.env` that contains the paths to the downloaded imod_collectors.

- Check out the MetaSWAP lookup table with your Deltares credentials which resides at `https://repos.deltares.nl/repos/DSCTestbench/trunk/cases/e150_metaswap/f00_common/c00_common/LHM2016_v01vrz`
```sh
pixi run install-test-dependencies
```

- To run the tests it is advisable to have a `.env` file at the root of the project directory instead of modifying global environment variables.
The content of `.env` would then look similar to this with the variables `IMOD_COLLECTOR_DEVEL`, `IMOD_COLLECTOR_REGRESSION` and `METASWAP_LOOKUP_TABLE` adjusted to your local machine:

```sh
IMOD_COLLECTOR_DEVEL='D:\checkouts\imod_collector_devel'
IMOD_COLLECTOR_REGRESSION='D:\checkouts\imod_collector_regression'
METASWAP_LOOKUP_TABLE='D:\checkouts\DSCtestbench\cases\e150_metaswap\f00_common\c00_common\LHM2016_v01vrz'

# Specify an absolute path here to use a packaged version of iMOD Coupler
IMOD_COUPLER_EXEC_DEVEL='imodc'
IMOD_COUPLER_EXEC_REGRESSION='${IMOD_COLLECTOR_REGRESSION}/imod_coupler/imodc.exe'
METASWAP_DLL_DEP_DIR_DEVEL='${IMOD_COLLECTOR_DEVEL}/metaswap'
METASWAP_DLL_DEP_DIR_REGRESSION='${IMOD_COLLECTOR_REGRESSION}/metaswap'
METASWAP_DLL_DEVEL='${IMOD_COLLECTOR_DEVEL}/metaswap/MetaSWAP.dll'
METASWAP_DLL_REGRESSION='${IMOD_COLLECTOR_REGRESSION}/metaswap/MetaSWAP.dll'
MODFLOW_DLL_DEVEL='${IMOD_COLLECTOR_DEVEL}/modflow6/libmf6.dll'
MODFLOW_DLL_REGRESSION='${IMOD_COLLECTOR_REGRESSION}/modflow6/libmf6.dll'
RIBASIM_DLL_DEP_DIR_DEVEL='${IMOD_COLLECTOR_DEVEL}/ribasim/bin'
RIBASIM_DLL_DEP_DIR_REGRESSION='${IMOD_COLLECTOR_REGRESSION}/ribasim/bin'
RIBASIM_DLL_DEVEL='${IMOD_COLLECTOR_DEVEL}/ribasim/bin/libribasim.dll'
RIBASIM_DLL_REGRESSION='${IMOD_COLLECTOR_REGRESSION}/ribasim/bin/libribasim.dll'
```
`install-test-dependencies` creates a `.env` file in the root of the project with the required environment variables pointing to the paths of imod_collector that can be found in the `.pixi` folder.

- The tests can then be run with:

```sh
pixi run tests
```
```sh
pixi run tests
```

- Lint the codebase with:

```sh
pixi run lint
```
```sh
pixi run lint
```

- When developing with visual studio code, it is recommended to open the application via `open-vscode.bat`.
This will open the application in a new vscode window with the correct environment variables set.
Expand Down
40 changes: 40 additions & 0 deletions pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ install-ribasim-api = "pip install git+https://github.com/Deltares/Ribasim.git/#
install-ribasim-testmodels = "pip install git+https://github.com/Deltares/Ribasim.git/#subdirectory=python/ribasim_testmodels"
install-primod = "pip install --no-deps --editable pre-processing"
install-imodc = "pip install --no-deps --editable ."
install-metaswap-testmodels = "svn checkout https://repos.deltares.nl/repos/DSCTestbench/trunk/cases/e150_metaswap/f00_common/c00_common/LHM2016_v01vrz .imod_collector/e150_metaswap"
install-imod-collector = "python scripts/download_imod_collector.py"
install-imod-collector-regression = "python scripts/download_imod_collector.py regression"
generate-env-file = "python scripts/generate_env_file.py"
install = { depends_on = [
"install-ribasim-python",
"install-ribasim-api",
"install-ribasim-testmodels",
"install-primod",
"install-imodc",
] }
install-test-dependencies = { depends_on = [
"install-metaswap-testmodels",
"install-imod-collector",
"install-imod-collector-regression",
"generate-env-file",
] }
update-git-dependencies = """pip install --upgrade --force-reinstall --no-deps
git+https://github.com/Deltares/Ribasim.git/#subdirectory=python/ribasim
git+https://github.com/Deltares/Ribasim.git/#subdirectory=python/ribasim_api
Expand Down Expand Up @@ -58,5 +68,7 @@ rasterio = ">=1.0"
ruff = "*"
scipy = "*"
tomli-w = "*"
tqdm = "*"
twine = "*"
types-requests = "*"
xmipy = "*"
73 changes: 73 additions & 0 deletions scripts/download_imod_collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import os
import sys
import xml.etree.ElementTree as ET
import zipfile
from pathlib import Path

import requests
from tqdm import tqdm


def download_imod_collector(tag: str | None) -> None:
build_id, build_number = _get_build_info(tag)
folder_name = (tag or "develop") + f"_{build_number}"
target_folder = Path(".imod_collector") / folder_name

if target_folder.exists():
print(
f"iMOD collector already downloaded at '{target_folder}', remove the folder if you want to enforce re-downloading."
)
return

token = os.environ["TEAMCITY_TOKEN"]
response = requests.get(
f"https://dpcbuild.deltares.nl/app/rest/builds/{build_id}/artifacts/content/imod_coupler_windows.zip",
headers={"Authorization": f"Bearer {token}"},
stream=True,
)
response.raise_for_status()

zip_path = Path(".pixi/imod_coupler_windows.zip")
_download_to_file(response, zip_path)
_unzip_to_target(target_folder, zip_path)

os.remove(zip_path)


def _get_build_info(tag: str | None) -> tuple[str, str]:
token = os.environ["TEAMCITY_TOKEN"]
tag_string = f",tag:{tag}" if tag else ""
info_url = f"https://dpcbuild.deltares.nl/app/rest/builds/buildType:iMOD6_IMOD6collectorDaily_ReleaseX64,count:1,branch:main,status:SUCCESS{tag_string}"

info_response = requests.get(
info_url,
headers={"Authorization": f"Bearer {token}"},
)
info_response.raise_for_status()
info_xml = ET.fromstring(info_response.content)
return info_xml.attrib["id"], info_xml.attrib["number"]


def _download_to_file(response: requests.Response, target_path: Path) -> None:
with open(target_path, "wb") as f:
progress_bar = tqdm(
total=int(response.headers["Content-Length"]),
unit_scale=True,
unit="B",
unit_divisor=1024,
desc="Downloading iMOD collector",
)
for chunk in response.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
progress_bar.update(len(chunk))


def _unzip_to_target(target_folder: Path, source_path: Path) -> None:
with zipfile.ZipFile(source_path) as z:
for file in tqdm(z.namelist(), desc="Extracting iMOD collector", unit="files"):
z.extract(file, target_folder)


if __name__ == "__main__":
download_imod_collector(sys.argv[1] if len(sys.argv) > 1 else None)
49 changes: 49 additions & 0 deletions scripts/generate_env_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from pathlib import Path

import jinja2


def generate_env_file() -> None:
template_generator = jinja2.Environment(
loader=jinja2.FileSystemLoader("scripts/templates"),
autoescape=True,
)
template = template_generator.get_template(".env.jinja")
with open(".env", "w") as f:
f.write(
template.render(
imod_collector_dev_path=_get_imod_collector_path("develop").resolve(),
imod_collector_regression_path=_get_imod_collector_path(
"regression"
).resolve(),
metaswap_lookup_table_path=_get_metaswap_path().resolve(),
)
)


def _get_imod_collector_path(tag: str) -> Path:
"""
Find an existing path of imod_collector.
Extract the numeric suffix from each path and find the path with the highest number
"""
search_path = Path(".imod_collector")
paths = search_path.glob(f"{tag}_*")
if not paths:
raise ValueError(f"No paths found for tag '{tag}'")
paths_with_numbers = [(path, int(path.name.split("_")[1])) for path in paths]
if not paths_with_numbers:
raise ValueError(f"No numeric suffixes found in paths for tag {tag}")

path_with_highest_number = max(paths_with_numbers, key=lambda x: x[1])[0]
return Path(path_with_highest_number)


def _get_metaswap_path() -> Path:
metaswap_path = Path(".imod_collector/e150_metaswap")
if not metaswap_path.exists():
raise ValueError(f"Metaswap lookup table not found at {metaswap_path}")
return metaswap_path


if __name__ == "__main__":
generate_env_file()
17 changes: 17 additions & 0 deletions scripts/templates/.env.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
IMOD_COLLECTOR_DEVEL="{{ imod_collector_dev_path|replace("\\", "/") }}"
IMOD_COLLECTOR_REGRESSION="{{ imod_collector_regression_path|replace("\\", "/") }}"
METASWAP_LOOKUP_TABLE="{{ metaswap_lookup_table_path|replace("\\", "/") }}"

# Specify an absolute path here to use a packaged version of iMOD Coupler
IMOD_COUPLER_EXEC_DEVEL="imodc"
IMOD_COUPLER_EXEC_REGRESSION="${IMOD_COLLECTOR_REGRESSION}/imod_coupler/imodc.exe"
METASWAP_DLL_DEP_DIR_DEVEL="${IMOD_COLLECTOR_DEVEL}/metaswap"
METASWAP_DLL_DEP_DIR_REGRESSION="${IMOD_COLLECTOR_REGRESSION}/metaswap"
METASWAP_DLL_DEVEL="${IMOD_COLLECTOR_DEVEL}/metaswap/MetaSWAP.dll"
METASWAP_DLL_REGRESSION="${IMOD_COLLECTOR_REGRESSION}/metaswap/MetaSWAP.dll"
MODFLOW_DLL_DEVEL="${IMOD_COLLECTOR_DEVEL}/modflow6/libmf6.dll"
MODFLOW_DLL_REGRESSION="${IMOD_COLLECTOR_REGRESSION}/modflow6/libmf6.dll"
RIBASIM_DLL_DEP_DIR_DEVEL="${IMOD_COLLECTOR_DEVEL}/ribasim/bin"
RIBASIM_DLL_DEP_DIR_REGRESSION="${IMOD_COLLECTOR_REGRESSION}/ribasim/bin"
RIBASIM_DLL_DEVEL="${IMOD_COLLECTOR_DEVEL}/ribasim/bin/libribasim.dll"
RIBASIM_DLL_REGRESSION="${IMOD_COLLECTOR_REGRESSION}/ribasim/bin/libribasim.dll"

0 comments on commit bedab9b

Please sign in to comment.