diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b58b5b32..7bbecf3d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1 +1 @@ -See the following [contributing guide](../docs/contributing.md). +See the following [contributing guide](https://virtualship.oceanparcels.org/en/latest/contributing.html). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cf39a99..6f3e2228 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,13 +21,6 @@ env: FORCE_COLOR: 3 jobs: - # pylint: - # name: Format - # runs-on: ubuntu-latest - # steps: - # - name: Run PyLint - # run: pipx run nox -s pylint -- --output-format=github - tests: name: tests (${{ matrix.runs-on }} | Python ${{ matrix.python-version }}) runs-on: ${{ matrix.runs-on }} @@ -48,7 +41,7 @@ jobs: create-args: >- python=${{matrix.python-version}} - - run: pip install . --no-deps --no-build-isolation + - run: pip install . --no-deps - name: Test package run: >- diff --git a/.gitignore b/.gitignore index ca9a1bd7..008f448d 100644 --- a/.gitignore +++ b/.gitignore @@ -175,3 +175,4 @@ cython_debug/ src/virtualship/_version_setup.py .vscode/ +.DS_Store diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 10735273..01f7df4e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,7 +10,7 @@ build: jobs: pre_build: - pip install . - # - sphinx-build -b linkcheck docs/ _build/linkcheck + - sphinx-build -b linkcheck docs/ _build/linkcheck - sphinx-apidoc -o docs/api/ --module-first --no-toc --force src/virtualship conda: diff --git a/README.md b/README.md index 1ecf867a..5b26eb3b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ +[![Anaconda-release](https://anaconda.org/conda-forge/virtualship/badges/version.svg)](https://anaconda.org/conda-forge/virtualship/) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/virtualship) +[![unit-tests](https://github.com/OceanParcels/virtualship/actions/workflows/ci.yml/badge.svg)](https://github.com/OceanParcels/virtualship/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/OceanParcels/virtualship/graph/badge.svg?token=SLGLN8QBLW)](https://codecov.io/gh/OceanParcels/virtualship) + + + --- @@ -45,7 +52,7 @@ conda activate ship which creates an environment named `ship` with the latest version of `virtualship`. You can replace `ship` with any name you like. -For a development installation, please follow the instructions detailed in the [contributing page](.github/CONTRIBUTING.md). +For a development installation, please follow the instructions detailed in the [contributing page](https://virtualship.oceanparcels.org/en/latest/contributing.html). ## Usage @@ -101,3 +108,7 @@ For examples, see [the tutorials section of our documentation](https://virtualsh ## Input data The scripts are written to work with [A-grid ocean data from the Copernicus Marine Service](https://data.marine.copernicus.eu/product/GLOBAL_ANALYSISFORECAST_PHY_001_024/description). + +## Source code + +The code for this project is [hosted on GitHub](https://github.com/OceanParcels/virtualship). diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..faaceb86 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true +comment: false +# When modifying this file, please validate using +# curl -X POST --data-binary @codecov.yml https://codecov.io/validate diff --git a/docs/conf.py b/docs/conf.py index d9cfa43f..5992e2fc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -47,15 +47,13 @@ "source_repository": "https://github.com/OceanParcels/virtualship", "source_branch": "main", "source_directory": "docs/", -} - -html_static_path = ["_static"] -html_theme_options = { "light_logo": "virtual_ship_logo.png", "dark_logo": "virtual_ship_logo_inverted.png", "sidebar_hide_name": True, } +html_static_path = ["_static"] + myst_enable_extensions = [ "colon_fence", ] diff --git a/docs/index.md b/docs/index.md index 63c04ec4..cbcf94ea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,6 +8,7 @@ Home quickstart tutorials/index contributing +OceanParcels ``` ```{include} ../README.md diff --git a/environment.yml b/environment.yml index 0865f534..bf9f7d82 100644 --- a/environment.yml +++ b/environment.yml @@ -10,6 +10,7 @@ dependencies: - numpy >=1, < 2 - pydantic >=2, <3 - pip + - pyyaml # linting - pre-commit diff --git a/meta.yaml b/meta.yaml deleted file mode 100644 index 21df09f1..00000000 --- a/meta.yaml +++ /dev/null @@ -1,30 +0,0 @@ -package: - name: virtualship - version: 0.0.2 - -source: - path: virtualship - -build: - entry_points: - - do_expedition = virtualship.cli.do_expedition:main - -requirements: - run: - - python >=3.10 - - parcels >=3,<4 - - pyproj >=3,<4 - - sortedcontainers ==2.4.0 - - opensimplex ==0.4.5 - - numpy >=1,<2 - -about: - home: https://oceanparcels.org/ - license: MIT - license_file: LICENSE - summary: > - Code for the Virtual Ship Classroom, where Marine Scientists can combine - Copernicus Marine Data with an OceanParcels ship to go on a virtual expedition. - description: > - Code for the Virtual Ship Classroom, where Marine Scientists can combine - Copernicus Marine Data with an OceanParcels ship to go on a virtual expedition. diff --git a/pyproject.toml b/pyproject.toml index 775ba2a1..0aded55a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "opensimplex == 0.4.5", "numpy >=1, < 2", "pydantic >=2, <3", + "PyYAML", ] [project.urls] @@ -41,9 +42,12 @@ Documentation = "https://virtualship.oceanparcels.org/" "Bug Tracker" = "https://github.com/OceanParcels/virtualship/issues" Changelog = "https://github.com/OceanParcels/virtualship/releases" + [tool.setuptools.packages.find] where = ["src"] +[tool.setuptools.package-data] +virtualship = ["*.yaml"] [tool.setuptools_scm] version_file = "src/virtualship/_version_setup.py" diff --git a/src/virtualship/cli/commands.py b/src/virtualship/cli/commands.py index 0764bb3a..9f218e6f 100644 --- a/src/virtualship/cli/commands.py +++ b/src/virtualship/cli/commands.py @@ -2,7 +2,9 @@ import click +from virtualship import utils from virtualship.expedition.do_expedition import do_expedition +from virtualship.utils import SCHEDULE, SHIP_CONFIG @click.command( @@ -11,11 +13,29 @@ @click.argument( "path", type=click.Path(exists=False, file_okay=False, dir_okay=True), - # help="Expedition directory", ) def init(path): """Entrypoint for the tool.""" - raise NotImplementedError("Not implemented yet.") + path = Path(path) + path.mkdir(exist_ok=True) + + config = path / SHIP_CONFIG + schedule = path / SCHEDULE + + if config.exists(): + raise FileExistsError( + f"File '{config}' already exist. Please remove it or choose another directory." + ) + + if schedule.exists(): + raise FileExistsError( + f"File '{schedule}' already exist. Please remove it or choose another directory." + ) + + config.write_text(utils.get_example_config()) + schedule.write_text(utils.get_example_schedule()) + + click.echo(f"Created '{config.name}' and '{schedule.name}' at {path}.") @click.command( diff --git a/src/virtualship/expedition/do_expedition.py b/src/virtualship/expedition/do_expedition.py index 0b43ee8e..1efd92f9 100644 --- a/src/virtualship/expedition/do_expedition.py +++ b/src/virtualship/expedition/do_expedition.py @@ -6,6 +6,8 @@ import pyproj +from virtualship.utils import CHECKPOINT, SCHEDULE, SHIP_CONFIG + from .checkpoint import Checkpoint from .expedition_cost import expedition_cost from .input_data import InputData @@ -109,7 +111,7 @@ def do_expedition(expedition_dir: str | Path) -> None: def _get_ship_config(expedition_dir: Path) -> ShipConfig | None: - file_path = expedition_dir.joinpath("ship_config.yaml") + file_path = expedition_dir.joinpath(SHIP_CONFIG) try: return ShipConfig.from_yaml(file_path) except FileNotFoundError: @@ -129,7 +131,7 @@ def _load_input_data(expedition_dir: Path, ship_config: ShipConfig) -> InputData def _get_schedule(expedition_dir: Path) -> Schedule | None: - file_path = expedition_dir.joinpath("schedule.yaml") + file_path = expedition_dir.joinpath(SCHEDULE) try: return Schedule.from_yaml(file_path) except FileNotFoundError: @@ -138,7 +140,7 @@ def _get_schedule(expedition_dir: Path) -> Schedule | None: def _load_checkpoint(expedition_dir: Path) -> Checkpoint | None: - file_path = expedition_dir.joinpath("checkpoint.yaml") + file_path = expedition_dir.joinpath(CHECKPOINT) try: return Checkpoint.from_yaml(file_path) except FileNotFoundError: @@ -146,5 +148,5 @@ def _load_checkpoint(expedition_dir: Path) -> Checkpoint | None: def _save_checkpoint(checkpoint: Checkpoint, expedition_dir: Path) -> None: - file_path = expedition_dir.joinpath("checkpoint.yaml") + file_path = expedition_dir.joinpath(CHECKPOINT) checkpoint.to_yaml(file_path) diff --git a/src/virtualship/static/__init__.py b/src/virtualship/static/__init__.py new file mode 100644 index 00000000..07770481 --- /dev/null +++ b/src/virtualship/static/__init__.py @@ -0,0 +1 @@ +"""Module to put static assets. Should not be used for data.""" diff --git a/src/virtualship/static/schedule.yaml b/src/virtualship/static/schedule.yaml new file mode 100644 index 00000000..0db1d2af --- /dev/null +++ b/src/virtualship/static/schedule.yaml @@ -0,0 +1,16 @@ +waypoints: + - instrument: CTD + location: + latitude: 0 + longitude: 0 + time: 2023-01-01 00:00:00 + - instrument: DRIFTER + location: + latitude: 0.01 + longitude: 0.01 + time: 2023-01-01 01:00:00 + - instrument: ARGO_FLOAT + location: + latitude: 0.02 + longitude: 0.02 + time: 2023-01-01 02:00:00 diff --git a/src/virtualship/static/ship_config.yaml b/src/virtualship/static/ship_config.yaml new file mode 100644 index 00000000..c057d6b5 --- /dev/null +++ b/src/virtualship/static/ship_config.yaml @@ -0,0 +1,21 @@ +ship_speed_meter_per_second: 5.14 +adcp_config: + num_bins: 40 + max_depth_meter: -1000.0 + period_minutes: 5.0 +argo_float_config: + cycle_days: 10.0 + drift_days: 9.0 + drift_depth_meter: -1000.0 + max_depth_meter: -2000.0 + min_depth_meter: 0.0 + vertical_speed_meter_per_second: -0.1 +ctd_config: + max_depth_meter: -2000.0 + min_depth_meter: -11.0 + stationkeeping_time_minutes: 20.0 +drifter_config: + depth_meter: 0.0 + lifetime_minutes: 40320.0 +ship_underwater_st_config: + period_minutes: 5.0 diff --git a/src/virtualship/utils.py b/src/virtualship/utils.py new file mode 100644 index 00000000..a8579bd5 --- /dev/null +++ b/src/virtualship/utils.py @@ -0,0 +1,23 @@ +from functools import lru_cache +from importlib.resources import files + +SCHEDULE = "schedule.yaml" +SHIP_CONFIG = "ship_config.yaml" +CHECKPOINT = "checkpoint.yaml" + + +def load_static_file(name: str) -> str: + """Load static file from the ``virtualship.static`` module by file name.""" + return files("virtualship.static").joinpath(name).read_text(encoding="utf-8") + + +@lru_cache(None) +def get_example_config() -> str: + """Get the example configuration file.""" + return load_static_file(SHIP_CONFIG) + + +@lru_cache(None) +def get_example_schedule() -> str: + """Get the example schedule file.""" + return load_static_file(SCHEDULE) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..ef39ba8d --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,41 @@ +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from virtualship.cli.commands import init +from virtualship.utils import SCHEDULE, SHIP_CONFIG + + +def test_init(): + runner = CliRunner() + with runner.isolated_filesystem(): + result = runner.invoke(init, ["."]) + assert result.exit_code == 0 + config = Path(SHIP_CONFIG) + schedule = Path(SCHEDULE) + + assert config.exists() + assert schedule.exists() + + +def test_init_existing_config(): + runner = CliRunner() + with runner.isolated_filesystem(): + config = Path(SHIP_CONFIG) + config.write_text("test") + + with pytest.raises(FileExistsError): + result = runner.invoke(init, ["."]) + raise result.exception + + +def test_init_existing_schedule(): + runner = CliRunner() + with runner.isolated_filesystem(): + schedule = Path(SCHEDULE) + schedule.write_text("test") + + with pytest.raises(FileExistsError): + result = runner.invoke(init, ["."]) + raise result.exception diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..b0d00be9 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,26 @@ +from virtualship.expedition import Schedule, ShipConfig +from virtualship.utils import get_example_config, get_example_schedule + + +def test_get_example_config(): + assert len(get_example_config()) > 0 + + +def test_get_example_schedule(): + assert len(get_example_schedule()) > 0 + + +def test_valid_example_config(tmp_path): + path = tmp_path / "test.yaml" + with open(path, "w") as file: + file.write(get_example_config()) + + ShipConfig.from_yaml(path) + + +def test_valid_example_schedule(tmp_path): + path = tmp_path / "test.yaml" + with open(path, "w") as file: + file.write(get_example_schedule()) + + Schedule.from_yaml(path)