From e85b08b4090f65959e9cf374787e723975f20b11 Mon Sep 17 00:00:00 2001 From: anusha c s Date: Thu, 21 May 2026 14:16:55 +0530 Subject: [PATCH 01/12] Add missing Issue templates,PR and LICENSE --- .github/ISSUE_TEMPLATE/bug_report.md | 33 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 23 +++ .github/pull_request_template.md | 10 ++ LICENSE | 202 ++++++++++++++++++++++ 4 files changed, 268 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 LICENSE diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..18378b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +## Environment + +- OS: +- Python version: +- Package version: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..12a60a6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea or enhancement +title: '' +labels: enhancement +assignees: '' + +--- + +## Feature Description +Describe the feature or enhancement. + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8a3f563 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,10 @@ +# Overview + +## Related Issue + +## Additional Information + +## Checklist + +- [ ] Code adheres to the project's style guidelines. +- [ ] All automated checks have passed. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file From e3d845e1ad5d98a8877ed845d410fc6467852ac7 Mon Sep 17 00:00:00 2001 From: anusha c s Date: Thu, 21 May 2026 14:24:14 +0530 Subject: [PATCH 02/12] update the bug and feature report template to improve the clarity --- .github/ISSUE_TEMPLATE/bug_report.md | 3 +-- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 18378b3..5cd1022 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,8 +23,7 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -## Environment - +**Environment** - OS: - Python version: - Package version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 12a60a6..cfff02b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,7 +7,7 @@ assignees: '' --- -## Feature Description +**Feature Description** Describe the feature or enhancement. **Is your feature request related to a problem? Please describe.** From 11745bf57375715e908a96476cb3d416083bbe4b Mon Sep 17 00:00:00 2001 From: anusha c s Date: Tue, 9 Jun 2026 18:09:23 +0530 Subject: [PATCH 03/12] created CHANGELOG md file --- CHANGELOG.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c413759 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [v0.1.3] - 2026-01-21 + +### Added + +- `__init__(self, **kwargs)` constructor added to `ExecutionHandler` with automatic attribute assignment for `job_id`, `outputs`, and `results` +- New `set_job_id(job_id)` method on `ExecutionHandler` for runtime job ID assignment + +### Changed + +- Package version is now dynamically retrieved from package metadata instead of being hardcoded in `__init__.py` +- Graceful fallback to `"unknown"` if package metadata is unavailable + +## [v0.1.2] - 2026-01-19 + +### Added + +- Created `zoo_runner_common/` package directory with proper `__init__.py` and explicit exports + +### Changed + +- Moved all modules (`base_runner.py`, `handlers.py`, `zoo_conf.py`, `zoostub.py`) into `zoo_runner_common/` package directory +- All imports updated to use package-style: `from zoo_runner_common import ...` +- Updated `pyproject.toml` with `setuptools.packages.find` configuration, switching from `py-modules` to package-based discovery + +### Removed + +- Root-level `__init__.py` removed for cleaner structure + +## [v0.1.1] - 2026-01-18 + +### Added + +- Added PyPI classifiers: Development Status (Beta), License, Python 3.10/3.11/3.12 support, Audience, Topic +- Added keywords: `zoo-project`, `cwl`, `runner`, `workflow`, `ogc`, `api`, `processes` +- Added project URLs for Homepage, Documentation, Issues, and Changelog +- Added Gérald Fenoy as co-author + +### Changed + +- License updated from BSD-3-Clause to Apache-2.0 for consistency across the ZOO-Project ecosystem + +## [v0.1.0] - 2026-01-18 + +### Added + +- Initial release of `zoo-runner-common` as a standalone Python package +- `BaseRunner` with 8+ shared methods: `get_workflow_id()`, `get_workflow_inputs()`, `get_max_cores()`, `get_max_ram()`, `get_volume_size()`, `assert_parameters()`, `get_processing_parameters()`, `get_namespace_name()`, `finalize()` +- New `ExecutionHandler` abstract class (`handlers.py`) with hooks: `pre_execution_hook()`, `post_execution_hook()`, `get_secrets()`, `get_pod_env_vars()`, `get_pod_node_selector()`, `handle_outputs()`, `get_additional_parameters()` +- `ZooConf` improvements: support for complex types (arrays, OGC bbox), NULL value handling, multiple file formats, CWL v1.2 parsing, scatter operation support +- `ZooStub` extended with all ZOO status codes (ACCEPTED, STARTED, PAUSED, DEPLOYED, etc.) and structured logging via `loguru` +- `loguru>=0.7.0` added as a dependency +- MkDocs Material documentation site with getting started guide, user guide, API reference, and developer guide +- GitHub Actions workflow for automatic documentation deployment to GitHub Pages +- GitHub Actions workflow for automated PyPI publishing on release with Trusted Publishing support +- `ruff.toml` configuration for linting (pycodestyle, pyflakes, isort, pyupgrade) +- Complete `.gitignore` for Python projects + +[Unreleased]: https://github.com/ZOO-Project/zoo-runner-common/compare/v0.1.3...HEAD +[v0.1.3]: https://github.com/ZOO-Project/zoo-runner-common/compare/v0.1.2...v0.1.3 +[v0.1.2]: https://github.com/ZOO-Project/zoo-runner-common/compare/v0.1.1...v0.1.2 +[v0.1.1]: https://github.com/ZOO-Project/zoo-runner-common/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/ZOO-Project/zoo-runner-common/releases/tag/v0.1.0 \ No newline at end of file From eef48788cac6eef8469e934cfe68e75eb2e18799 Mon Sep 17 00:00:00 2001 From: anusha c s Date: Tue, 16 Jun 2026 20:52:02 +0530 Subject: [PATCH 04/12] migrate to hatch --- pyproject.toml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9107850..7ba9790 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools>=68", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["hatchling"] +build-backend = "hatchling.build" [project] name = "zoo-runner-common" @@ -25,7 +25,11 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "loguru>=0.7.0", + "loguru", + "attrs", + "PyYAML", + "click", + "cwl-utils>=0.14", ] [project.urls] @@ -35,6 +39,7 @@ Repository = "https://github.com/ZOO-Project/zoo-runner-common" Issues = "https://github.com/ZOO-Project/zoo-runner-common/issues" Changelog = "https://github.com/ZOO-Project/zoo-runner-common/blob/main/CHANGELOG.md" -[tool.setuptools.packages.find] -where = ["."] -include = ["zoo_runner_common*"] +[tool.hatch.build.targets.wheel] +packages = [ + "zoo_runner_common" +] From 6538fa7d11add208e455a7153c57f5dd495e71fe Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 17 Jun 2026 13:52:36 +0530 Subject: [PATCH 05/12] Implement hatch tes t and ruff check --- .github/workflows/package.yaml | 52 +++++++++ .github/workflows/publish-pypi.yml | 7 +- pyproject.toml | 21 +++- tests/test_base_runner.py | 167 +++++++++++++++++++++++++++++ tests/test_placeholder.py | 2 + 5 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/package.yaml create mode 100644 tests/test_base_runner.py create mode 100644 tests/test_placeholder.py diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml new file mode 100644 index 0000000..70d545d --- /dev/null +++ b/.github/workflows/package.yaml @@ -0,0 +1,52 @@ +name: Python Package + +on: + push: + branches: + - feature/runner-common + - main + pull_request: + branches: + - develop + - main + +permissions: + contents: read + +jobs: + ci: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install tooling + run: | + python -m pip install --upgrade pip + pip install hatch ruff + + - name: Ruff Check + run: ruff check . + + - name: Ruff Format Check + run: ruff format --check . + + - name: Run tests + run: hatch run test:test-q + + - name: Build package + run: hatch build \ No newline at end of file diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 54b429c..4ad9d13 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,6 +1,9 @@ name: Publish to PyPI on: + push: + branches: + - feature/runner-common release: types: [published] workflow_dispatch: @@ -34,7 +37,7 @@ jobs: - name: Install build tools run: | python -m pip install --upgrade pip - pip install build toml + pip install hatch - name: Update version (if manual trigger) if: github.event_name == 'workflow_dispatch' @@ -55,7 +58,7 @@ jobs: git push origin main --tags - name: Build package - run: python -m build + run: hatch build - name: Publish to PyPI (Trusted Publishing) uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/pyproject.toml b/pyproject.toml index 7ba9790..edcdf57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,22 @@ Repository = "https://github.com/ZOO-Project/zoo-runner-common" Issues = "https://github.com/ZOO-Project/zoo-runner-common/issues" Changelog = "https://github.com/ZOO-Project/zoo-runner-common/blob/main/CHANGELOG.md" -[tool.hatch.build.targets.wheel] -packages = [ - "zoo_runner_common" + +[tool.hatch.envs.default] +dependencies = [ + "loguru", + "attrs", + "PyYAML", + "click", + "cwl-utils>=0.14", ] + +[tool.hatch.envs.test] +dependencies = [ + "pytest", + "pytest-cov", +] + +[tool.hatch.envs.test.scripts] +test = "pytest {args}" +test-q = "pytest -q {args}" diff --git a/tests/test_base_runner.py b/tests/test_base_runner.py new file mode 100644 index 0000000..9ff8635 --- /dev/null +++ b/tests/test_base_runner.py @@ -0,0 +1,167 @@ +import pytest +from unittest.mock import MagicMock, patch + +from zoo_runner_common.base_runner import BaseRunner + +DUMMY_CWL = { + "cwlVersion": "v1.2", + "$graph": [ + {"class": "Workflow", "id": "#main", "inputs": [], "outputs": [], "steps": []} + ], +} +DUMMY_CONF = {"lenv": {"Identifier": "main", "usid": "test-123", "message": ""}} + + +class ConcreteRunner(BaseRunner): + def wrap(self): + return {"wrapped": True} + + def execute(self): + return "done" + + +@pytest.fixture +def runner(): + with patch("zoo_runner_common.base_runner.CWLWorkflow") as mock_cwl_cls: + mock_cwl = MagicMock() + mock_cwl.get_workflow.return_value = MagicMock(inputs=[]) + mock_cwl.eval_resource.return_value = { + "coresMin": [], + "coresMax": [], + "ramMin": [], + "ramMax": [], + "tmpdirMin": [], + "tmpdirMax": [], + "outdirMin": [], + "outdirMax": [], + } + mock_cwl_cls.return_value = mock_cwl + yield ConcreteRunner( + cwl=DUMMY_CWL, inputs={}, conf=DUMMY_CONF, outputs={"stac": {"value": None}} + ) + + +def test_get_workflow_id(runner): + assert runner.get_workflow_id() == "main" + + +def test_update_status_calls_zoo(runner, capsys): + runner.update_status(10, "testing") + out = capsys.readouterr().out + assert "Status 10" in out + assert runner.conf.conf["lenv"]["message"] == "testing" + + +def test_get_namespace_name_format(runner): + name = runner.get_namespace_name() + assert name.startswith("main-") + # format: "{workflow_id}-{8 hex chars}" + assert len(name) == len("main-") + 8 + + +def test_get_namespace_name_is_stable(runner): + assert runner.get_namespace_name() == runner.get_namespace_name() + + +def test_get_namespace_name_is_lowercase(runner): + assert runner.get_namespace_name() == runner.get_namespace_name().lower() + + +def test_validate_inputs_returns_true(runner): + assert runner.validate_inputs() is True + + +def test_log_output_does_not_crash(runner): + runner.log_output({"key": "value"}) + + +def test_execute(runner): + assert runner.execute() == "done" + + +def test_wrap(runner): + assert runner.wrap() == {"wrapped": True} + + +def test_get_max_cores_uses_env_default(runner, monkeypatch): + monkeypatch.setenv("DEFAULT_MAX_CORES", "8") + assert runner.get_max_cores() == 8 + + +def test_get_max_ram_returns_mi_format(runner, monkeypatch): + monkeypatch.setenv("DEFAULT_MAX_RAM", "2048") + assert runner.get_max_ram() == "2048Mi" + + +def test_get_volume_size_returns_env_default_when_unit_matches(runner, monkeypatch): + # When the unit in DEFAULT_VOLUME_SIZE matches the requested unit, value is returned as-is + monkeypatch.setenv("DEFAULT_VOLUME_SIZE", "20Mi") + assert runner.get_volume_size() == "20Mi" # unit="Mi" by default + + +def test_get_volume_size_fallback_to_10_when_unit_mismatch(runner, monkeypatch): + # When unit doesn't match the default string (e.g. asking for Mi but default is Gi), + # the method returns hardcoded "10{unit}" + monkeypatch.setenv("DEFAULT_VOLUME_SIZE", "20Gi") + assert runner.get_volume_size(unit="Mi") == "10Mi" + + +def test_get_volume_size_gi_unit_uses_default(runner, monkeypatch): + monkeypatch.setenv("DEFAULT_VOLUME_SIZE", "20Gi") + assert runner.get_volume_size(unit="Gi") == "20Gi" + + +def test_assert_parameters_true_when_no_required_inputs(runner): + runner.workflow.get_workflow_inputs.return_value = [] + assert runner.assert_parameters() is True + + +def test_assert_parameters_false_when_input_missing(runner): + runner.workflow.get_workflow_inputs.return_value = ["required_input"] + assert runner.assert_parameters() is False + + +def test_prepare_calls_pre_execution_hook(runner): + handler = MagicMock() + handler.get_additional_parameters.return_value = {} + runner.execution_handler = handler + runner.prepare() + handler.pre_execution_hook.assert_called_once() + + +def test_prepare_returns_namespace_with_cwl_and_params(runner): + result = runner.prepare() + assert hasattr(result, "cwl") + assert hasattr(result, "params") + assert result.cwl == {"wrapped": True} + + +def test_finalize_calls_post_execution_hook_and_handle_outputs(runner): + handler = MagicMock() + runner.execution_handler = handler + runner.finalize("log", {"output": 1}, {}, []) + handler.post_execution_hook.assert_called_once_with("log", {"output": 1}, {}, []) + handler.handle_outputs.assert_called_once_with("log", {"output": 1}, {}, []) + + +def test_default_handler_has_all_required_methods(runner): + handler = runner.execution_handler + for method in [ + "pre_execution_hook", + "post_execution_hook", + "get_secrets", + "get_additional_parameters", + "get_pod_env_vars", + "get_pod_node_selector", + "handle_outputs", + "set_job_id", + ]: + assert callable(getattr(handler, method)), f"Missing method: {method}" + + +def test_zoo_conf_wraps_conf(runner): + assert runner.conf.conf is DUMMY_CONF + + +def test_legacy_zoo_conf_namespace(runner): + assert runner.zoo_conf.conf is DUMMY_CONF diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py new file mode 100644 index 0000000..3ada1ee --- /dev/null +++ b/tests/test_placeholder.py @@ -0,0 +1,2 @@ +def test_placeholder(): + assert True From 692c7e894e9a63c94a3aa15e6d45e20a5ba739cd Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 17 Jun 2026 14:06:43 +0530 Subject: [PATCH 06/12] reformatted files --- zoo_runner_common/__init__.py | 3 ++- zoo_runner_common/base_runner.py | 19 ++++++++++++---- zoo_runner_common/handlers.py | 6 ++--- zoo_runner_common/zoo_conf.py | 38 +++++++++++++++++++------------- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/zoo_runner_common/__init__.py b/zoo_runner_common/__init__.py index 279637e..e1b5c35 100644 --- a/zoo_runner_common/__init__.py +++ b/zoo_runner_common/__init__.py @@ -8,7 +8,7 @@ __all__ = [ "BaseRunner", "ZooConf", - "ZooInputs", + "ZooInputs", "ZooOutputs", "CWLWorkflow", "ExecutionHandler", @@ -17,6 +17,7 @@ try: from importlib.metadata import version + __version__ = version("zoo-runner-common") except Exception: __version__ = "unknown" diff --git a/zoo_runner_common/base_runner.py b/zoo_runner_common/base_runner.py index d16da06..83a21e4 100644 --- a/zoo_runner_common/base_runner.py +++ b/zoo_runner_common/base_runner.py @@ -100,7 +100,7 @@ def update_status(self, progress: int, message: str = ""): progress: Progress percentage (0-100) message: Status message to display """ - if hasattr(self.conf, 'conf') and "lenv" in self.conf.conf: + if hasattr(self.conf, "conf") and "lenv" in self.conf.conf: self.conf.conf["lenv"]["message"] = message zoo.update_status(self.conf.conf, progress) else: @@ -115,6 +115,7 @@ def get_namespace_name(self): """ if self.namespace_name is None: import uuid + unique_id = str(uuid.uuid4())[:8] self.namespace_name = f"{self.get_workflow_id()}-{unique_id}".lower() @@ -260,8 +261,16 @@ def get_volume_size(self, unit="Mi"): # Get max tmpdir and outdir in MB # Use Max if available, otherwise fall back to Min - tmpdir_max = max(resources["tmpdirMax"]) if resources["tmpdirMax"] else (max(resources["tmpdirMin"]) if resources["tmpdirMin"] else 0) - outdir_max = max(resources["outdirMax"]) if resources["outdirMax"] else (max(resources["outdirMin"]) if resources["outdirMin"] else 0) + tmpdir_max = ( + max(resources["tmpdirMax"]) + if resources["tmpdirMax"] + else (max(resources["tmpdirMin"]) if resources["tmpdirMin"] else 0) + ) + outdir_max = ( + max(resources["outdirMax"]) + if resources["outdirMax"] + else (max(resources["outdirMin"]) if resources["outdirMin"] else 0) + ) # Total in MB volume_size_mb = tmpdir_max + outdir_max @@ -315,7 +324,9 @@ def get_processing_parameters(self): Returns: dict: Processing parameters suitable for CWL execution """ - return self.inputs.get_processing_parameters(workflow=self.workflow.get_workflow()) + return self.inputs.get_processing_parameters( + workflow=self.workflow.get_workflow() + ) @abstractmethod def wrap(self): diff --git a/zoo_runner_common/handlers.py b/zoo_runner_common/handlers.py index 1e394e9..a980a84 100644 --- a/zoo_runner_common/handlers.py +++ b/zoo_runner_common/handlers.py @@ -20,9 +20,9 @@ class ExecutionHandler(ABC): def __init__(self, **kwargs): self.__dict__.update(kwargs) - self.job_id = getattr(self, 'job_id', None) - self.outputs = getattr(self, 'outputs', {}) - self.results = getattr(self, 'results', None) + self.job_id = getattr(self, "job_id", None) + self.outputs = getattr(self, "outputs", {}) + self.results = getattr(self, "results", None) def set_job_id(self, job_id): """Set the job ID for the execution.""" diff --git a/zoo_runner_common/zoo_conf.py b/zoo_runner_common/zoo_conf.py index 1b1199e..ebfbb51 100644 --- a/zoo_runner_common/zoo_conf.py +++ b/zoo_runner_common/zoo_conf.py @@ -29,12 +29,12 @@ class CWLWorkflow: def __init__(self, cwl, workflow_id): self.raw_cwl = cwl self.workflow_id = workflow_id - + # Load the entire CWL document and convert to v1.2 # Use load_cwl_from_yaml instead of load_document_by_yaml for proper version conversion from cwl_loader import load_cwl_from_yaml - - parsed_cwl = load_cwl_from_yaml(cwl, uri="io://", cwl_version='v1.2', sort=True) + + parsed_cwl = load_cwl_from_yaml(cwl, uri="io://", cwl_version="v1.2", sort=True) # Ensure self.cwl is always a list containing all CWL elements if not isinstance(parsed_cwl, list): @@ -71,7 +71,7 @@ def get_workflow_inputs(self, mandatory=False): for inp in self.get_workflow().inputs: if mandatory: # Use type_ instead of type (cwl-utils API change) - inp_type = getattr(inp, 'type_', getattr(inp, 'type', None)) + inp_type = getattr(inp, "type_", getattr(inp, "type", None)) if inp.default is not None or inp_type == ["null", "string"]: continue else: @@ -132,7 +132,7 @@ def get_resource_requirement(elem): if isinstance(hint, dict): if hint.get("class") == "ResourceRequirement": resource_requirement.append(ResourceRequirement.from_dict(hint)) - elif hasattr(hint, 'class_'): + elif hasattr(hint, "class_"): if hint.class_ == "ResourceRequirement": resource_requirement.append(hint) @@ -232,17 +232,19 @@ def get_input_value(self, key): def get_processing_parameters(self, workflow=None): """Returns a list with the input parameters keys - + Args: workflow: Optional CWL workflow object (currently unused, for future compatibility) """ import json - + res = {} allowed_types = ["integer", "float", "boolean", "double"] - + for key, value in self.inputs.items(): - if "format" in value and not("dataType" in value and value["dataType"] in allowed_types): + if "format" in value and not ( + "dataType" in value and value["dataType"] in allowed_types + ): res[key] = { "format": value["format"], "value": value["value"], @@ -275,21 +277,27 @@ def get_processing_parameters(self, workflow=None): if "isArray" in value and value["isArray"] == "true": res[key] = [] for i in range(len(value["value"])): - res[key].append({ - "format": value["mimeType"][i] if "mimeType" in value else "text/plain", - "value": value["value"][i], - }) + res[key].append( + { + "format": value["mimeType"][i] + if "mimeType" in value + else "text/plain", + "value": value["value"][i], + } + ) else: res[key] = { "format": value.get("mimeType", "text/plain"), - "value": value["value"] + "value": value["value"], } else: if "lowerCorner" in value and "upperCorner" in value: res[key] = { "format": "ogc-bbox", "bbox": json.loads(value["value"]), - "crs": value["crs"].replace("http://www.opengis.net/def/crs/OGC/1.3/", "") + "crs": value["crs"].replace( + "http://www.opengis.net/def/crs/OGC/1.3/", "" + ), } else: res[key] = value["value"] From 5801235c1d1dc240c39bc93426ffa815fcf41a9c Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 17 Jun 2026 14:44:04 +0530 Subject: [PATCH 07/12] Fix cwl_utils parser import --- tests/test_base_runner.py | 3 ++- zoo_runner_common/__init__.py | 2 +- zoo_runner_common/zoo_conf.py | 9 +++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_base_runner.py b/tests/test_base_runner.py index 9ff8635..ee479ed 100644 --- a/tests/test_base_runner.py +++ b/tests/test_base_runner.py @@ -1,6 +1,7 @@ -import pytest from unittest.mock import MagicMock, patch +import pytest + from zoo_runner_common.base_runner import BaseRunner DUMMY_CWL = { diff --git a/zoo_runner_common/__init__.py b/zoo_runner_common/__init__.py index e1b5c35..2fbdfa8 100644 --- a/zoo_runner_common/__init__.py +++ b/zoo_runner_common/__init__.py @@ -1,8 +1,8 @@ """ZOO-Project Runner Common - Shared utilities for CWL runners.""" from zoo_runner_common.base_runner import BaseRunner -from zoo_runner_common.zoo_conf import ZooConf, ZooInputs, ZooOutputs, CWLWorkflow from zoo_runner_common.handlers import ExecutionHandler +from zoo_runner_common.zoo_conf import CWLWorkflow, ZooConf, ZooInputs, ZooOutputs from zoo_runner_common.zoostub import ZooStub __all__ = [ diff --git a/zoo_runner_common/zoo_conf.py b/zoo_runner_common/zoo_conf.py index ebfbb51..3f2c575 100644 --- a/zoo_runner_common/zoo_conf.py +++ b/zoo_runner_common/zoo_conf.py @@ -2,8 +2,7 @@ import os import attr -import cwl_utils -from cwl_utils.parser import load_document_by_yaml +import cwl_utils.parser # useful class for hints in CWL @@ -31,7 +30,8 @@ def __init__(self, cwl, workflow_id): self.workflow_id = workflow_id # Load the entire CWL document and convert to v1.2 - # Use load_cwl_from_yaml instead of load_document_by_yaml for proper version conversion + # Use load_cwl_from_yaml instead of load_document_by_yaml + # for proper version conversion from cwl_loader import load_cwl_from_yaml parsed_cwl = load_cwl_from_yaml(cwl, uri="io://", cwl_version="v1.2", sort=True) @@ -234,7 +234,8 @@ def get_processing_parameters(self, workflow=None): """Returns a list with the input parameters keys Args: - workflow: Optional CWL workflow object (currently unused, for future compatibility) + workflow: Optional CWL workflow object (currently unused, + for future compatibility) """ import json From be39fac1c1014c80c1fd3211814c5244bf832d13 Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 17 Jun 2026 15:01:59 +0530 Subject: [PATCH 08/12] remove local branch --- .github/workflows/publish-pypi.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 4ad9d13..a23ef94 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,9 +1,6 @@ name: Publish to PyPI on: - push: - branches: - - feature/runner-common release: types: [published] workflow_dispatch: From a8d585555d8ee1b6191feb5dd3dbba770dd582ab Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 17 Jun 2026 17:27:26 +0530 Subject: [PATCH 09/12] ci: merge PyPI workflow into package workflow --- .github/workflows/package.yaml | 60 ++++++++++++++++++++++--- .github/workflows/publish-pypi.yml | 71 ------------------------------ pyproject.toml | 5 ++- zoo_runner_common/__about__.py | 1 + 4 files changed, 58 insertions(+), 79 deletions(-) delete mode 100644 .github/workflows/publish-pypi.yml create mode 100644 zoo_runner_common/__about__.py diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 70d545d..b01ac0f 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -1,12 +1,20 @@ -name: Python Package +name: package on: push: branches: - - feature/runner-common + - feature + - feature/** + - develop - main + - feature/runner-common + tags: + - v*.*.* + pull_request: branches: + - feature + - feature/** - develop - main @@ -15,6 +23,7 @@ permissions: jobs: ci: + if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/heads/') runs-on: ubuntu-latest strategy: @@ -26,27 +35,64 @@ jobs: - "3.12" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: pip - name: Install tooling run: | - python -m pip install --upgrade pip pip install hatch ruff - name: Ruff Check run: ruff check . - - name: Ruff Format Check + - name: Ruff Lint run: ruff format --check . - name: Run tests run: hatch run test:test-q + publish: + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + + environment: release + + permissions: + id-token: write + contents: read + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + cache: pip + + - name: Install Hatch + run: pip install hatch + + - name: Verify tag matches project version + run: | + TAG_VERSION="${GITHUB_REF_NAME#v}" + PROJECT_VERSION="$(hatch version)" + + echo "Tag version: ${TAG_VERSION}" + echo "Project version: ${PROJECT_VERSION}" + + if [ "${TAG_VERSION}" != "${PROJECT_VERSION}" ]; then + echo "Tag ${GITHUB_REF_NAME} does not match project version ${PROJECT_VERSION}" + exit 1 + fi + - name: Build package - run: hatch build \ No newline at end of file + run: hatch build + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml deleted file mode 100644 index a23ef94..0000000 --- a/.github/workflows/publish-pypi.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Publish to PyPI - -on: - release: - types: [published] - workflow_dispatch: - inputs: - version: - description: 'Version to publish (e.g., 1.0.0)' - required: true - type: string - -jobs: - publish: - runs-on: ubuntu-latest - # Strongly recommended: use a GitHub environment for additional security - environment: pypi - permissions: - contents: write - # IMPORTANT: this permission is mandatory for Trusted Publishing - id-token: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install build tools - run: | - python -m pip install --upgrade pip - pip install hatch - - - name: Update version (if manual trigger) - if: github.event_name == 'workflow_dispatch' - run: | - python << 'EOF' - import toml - with open('pyproject.toml', 'r') as f: - data = toml.load(f) - data['project']['version'] = '${{ inputs.version }}' - with open('pyproject.toml', 'w') as f: - toml.dump(data, f) - EOF - git config user.name "GitHub Actions" - git config user.email "actions@github.com" - git add pyproject.toml - git commit -m "chore: bump version to ${{ inputs.version }}" - git tag "v${{ inputs.version }}" - git push origin main --tags - - - name: Build package - run: hatch build - - - name: Publish to PyPI (Trusted Publishing) - uses: pypa/gh-action-pypi-publish@release/v1 - - - name: Create GitHub Release (if manual) - if: github.event_name == 'workflow_dispatch' - uses: softprops/action-gh-release@v1 - with: - tag_name: v${{ inputs.version }} - name: Release v${{ inputs.version }} - draft: false - prerelease: false - generate_release_notes: true diff --git a/pyproject.toml b/pyproject.toml index edcdf57..7caa686 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "zoo-runner-common" -version = "0.1.3" +dynamic = ["version"] description = "Shared utilities for ZOO-Project CWL runners" readme = "README.md" requires-python = ">=3.10" @@ -39,6 +39,9 @@ Repository = "https://github.com/ZOO-Project/zoo-runner-common" Issues = "https://github.com/ZOO-Project/zoo-runner-common/issues" Changelog = "https://github.com/ZOO-Project/zoo-runner-common/blob/main/CHANGELOG.md" +[tool.hatch.version] +path = "zoo_runner_common/__about__.py" + [tool.hatch.envs.default] dependencies = [ diff --git a/zoo_runner_common/__about__.py b/zoo_runner_common/__about__.py new file mode 100644 index 0000000..acf3be3 --- /dev/null +++ b/zoo_runner_common/__about__.py @@ -0,0 +1 @@ +__version__ = "0.1.3" \ No newline at end of file From ab9955506949b0a1cfbc97e1d6b222b85d2f7933 Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 17 Jun 2026 17:33:30 +0530 Subject: [PATCH 10/12] reformat code with ruff --- zoo_runner_common/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zoo_runner_common/__about__.py b/zoo_runner_common/__about__.py index acf3be3..ae73625 100644 --- a/zoo_runner_common/__about__.py +++ b/zoo_runner_common/__about__.py @@ -1 +1 @@ -__version__ = "0.1.3" \ No newline at end of file +__version__ = "0.1.3" From f63dae1d19ed08b7c9747d200dda5bff61b10ea4 Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 1 Jul 2026 19:07:22 +0530 Subject: [PATCH 11/12] add missing CONTRIBUTING.md and CODE_OF_CONDUCT.md --- .github/workflows/package.yaml | 1 - CODE_OF_CONDUCT.md | 4 + docs/contributing.md | 260 +++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 docs/contributing.md diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index b01ac0f..ea4f0ac 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -7,7 +7,6 @@ on: - feature/** - develop - main - - feature/runner-common tags: - v*.*.* diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..444bc03 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +Code of Conduct +=============== + +Contributors to ZOO-Project are expected to act respectfully toward others in accordance with [the OSGeo Code of Conduct](https://www.osgeo.org/resources/osgeo-code-of-conduct/). \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..9f6e0a5 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,260 @@ +# Contributing to zoo-runner-common + +Thank you for your interest in contributing to `zoo-runner-common`! This document provides guidelines for contributing to the project. + +## Overview + +`zoo-runner-common` provides shared utilities for ZOO-Project CWL runners, including `BaseRunner`, `ExecutionHandler`, `ZooConf`, `ZooInputs`, `ZooOutputs`, and `ZooStub`. The project uses [Hatch](https://hatch.pypa.io/) as its build and development tool. + +--- + +> **Important:** Always open your Pull Request against the `develop` branch, **not** `main`. +> Pull Requests targeting `main` directly will not be accepted. + +## Getting Started + +### Prerequisites + +- Python 3.10 or higher +- Git +- [Hatch](https://hatch.pypa.io/latest/install/) (`pip install hatch`) + +### Development Setup + +1. **Fork and Clone** + + ```bash + git clone https://github.com/ZOO-Project/zoo-runner-common.git + cd zoo-runner-common + ``` + +2. **Install Hatch** + + ```bash + pip install hatch + ``` + +3. **Enter the Default Development Environment** + + Hatch automatically creates and manages a virtual environment for you: + + ```bash + hatch shell + ``` + + This installs all dependencies defined under `[tool.hatch.envs.default]` in `pyproject.toml`. + +5. **Verify Installation** + + ```bash + python -c "from zoo_runner_common import BaseRunner; print('OK')" + ``` + +--- + +## Development Workflow + +### 1. Create a Branch + +```bash +git checkout -b feature/your-feature-name +``` + +Use these prefixes: + +- `feature/` — New features +- `fix/` — Bug fixes +- `docs/` — Documentation updates +- `refactor/` — Code refactoring +- `test/` — Test additions or fixes + +### 2. Make Changes + +Follow the coding standards described in the [Code Standards](#code-standards) section below. + +### 3. Run Tests + +The project uses `pytest` for testing via a dedicated Hatch environment: + +```bash +# Run tests +hatch run test:test + +# Run tests quietly +hatch run test:test-q +``` + +### 4. Update Documentation + +- Update docstrings in the source code +- Update relevant `.md` files in `docs/`, if present + +### 5. Commit Changes + +Write clear, conventional commit messages: + +```bash +git add . +git commit -m "feat: add new shared utility method to BaseRunner" +``` + +Commit types: + +- `feat` — New feature +- `fix` — Bug fix +- `docs` — Documentation only +- `style` — Formatting, no logic change +- `refactor` — Code restructuring +- `test` — Adding or updating tests +- `chore` — Maintenance, dependency updates + +### 6. Push and Create a Pull Request + +```bash +git push origin feature/your-feature-name +``` + +Then open a Pull Request on GitHub with: + +- **Base branch set to `develop`** — this is required +- A clear title and description +- Reference to any related issues (`Closes #123`) +- A summary of what changed and why +- Notes on any breaking changes + +> **Reminder:** The base branch of your PR must be `develop`, not `main`. +> `main` is only updated by maintainers when cutting a release from `develop`. + +--- + +## Hatch Environments + +The project defines two Hatch environments in `pyproject.toml`: + +| Environment | Purpose | Key Command | +|---|---|---| +| `default` | Day-to-day development | `hatch shell` | +| `test` | Running tests | `hatch run test:test` | + +--- + +## Code Standards + +### Python Style + +- Follow [PEP 8](https://peps.python.org/pep-0008/) +- Use **type hints** on all public methods +- Use **Google-style docstrings** +- Minimum Python version: **3.10** + +**Good example:** + +```python +def get_workflow_id(self) -> str: + """ + Return the workflow identifier from the ZOO configuration. + + Returns: + The workflow ID string. + + Raises: + ValueError: If the workflow ID is not set. + """ + if not self._workflow_id: + raise ValueError("Workflow ID is not set") + return self._workflow_id +``` + +### Error Handling + +Catch specific exceptions and use structured logging via `loguru`: + +```python +from loguru import logger + +def load_config(self, path: str) -> dict: + """Load configuration from a YAML file.""" + try: + with open(path) as f: + return yaml.safe_load(f) + except FileNotFoundError: + logger.warning(f"Config file not found: {path}") + return {} + except yaml.YAMLError as e: + logger.error(f"Invalid YAML in {path}: {e}") + raise +``` + +### Versioning + +The package version is managed in `zoo_runner_common/__about__.py`. Do **not** manually edit the version; it is updated as part of the release process. + +--- + +## Testing Guidelines + +Place unit tests under `tests/` and name files `test_*.py`: + +```python +# tests/test_base_runner.py +import unittest +from zoo_runner_common import BaseRunner + +class TestBaseRunner(unittest.TestCase): + + def test_initialization(self): + """Test that BaseRunner initializes correctly.""" + self.assertIsNotNone(BaseRunner) +``` + +--- + +## Release Process + +Releases are managed by project maintainers: + +1. Ensure all changes are merged into `develop` and tested +2. Update the version in `zoo_runner_common/__about__.py` +3. Update `CHANGELOG.md` +4. Merge `develop` into `main` +5. Create and push a release tag: + + ```bash + git tag v0.1.4 + git push origin v0.1.4 + ``` + +6. Build and publish the package: + + ```bash + hatch build + hatch publish + ``` + +--- + +## Getting Help + +- **Bug reports / feature requests**: [Open an issue](https://github.com/ZOO-Project/zoo-runner-common/issues) +- **Contact**: Email the maintainers + +--- + +## Code of Conduct + +We are committed to a welcoming and inclusive environment. When participating: + +- Be respectful of differing viewpoints and experiences +- Accept constructive criticism gracefully +- Focus on what is best for the project and community +- Show empathy towards other contributors + +--- + +## License + +By contributing, you agree that your contributions will be licensed under the **Apache License 2.0**, the same license as this project. + +--- + +Thank you for contributing! 🎉 From 4d9fa63acb1a651b9b356cc02b3b4bff83f23ee7 Mon Sep 17 00:00:00 2001 From: anusha c s Date: Wed, 1 Jul 2026 19:43:59 +0530 Subject: [PATCH 12/12] format imports in zoo_conf.py --- zoo_runner_common/zoo_conf.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/zoo_runner_common/zoo_conf.py b/zoo_runner_common/zoo_conf.py index ff65494..6915323 100644 --- a/zoo_runner_common/zoo_conf.py +++ b/zoo_runner_common/zoo_conf.py @@ -1,11 +1,10 @@ import inspect import os +import re import attr import cwl_utils.parser -import cwl_utils -import re # useful class for hints in CWL @attr.s @@ -211,7 +210,6 @@ def __init__(self, conf): class ZooInputs: - def __init__(self, inputs): # this conversion is necessary # because zoo converts array of length 1 to a string @@ -298,13 +296,13 @@ def get_processing_parameters(self, workflow=None): if "lowerCorner" in value and "upperCorner" in value: prefix_list = [ "http://www.opengis.net/def/crs/OGC/0/", - "http://www.opengis.net/def/crs/OGC/1.3/" + "http://www.opengis.net/def/crs/OGC/1.3/", ] pattern = re.compile("|".join(map(re.escape, prefix_list))) res[key] = { "format": "ogc-bbox", "bbox": json.loads(value["value"]), - "crs": pattern.sub("", value["crs"]) + "crs": pattern.sub("", value["crs"]), } else: res[key] = value["value"]