diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..5cd1022 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +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..cfff02b --- /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/.github/workflows/package.yaml b/.github/workflows/package.yaml new file mode 100644 index 0000000..ea4f0ac --- /dev/null +++ b/.github/workflows/package.yaml @@ -0,0 +1,97 @@ +name: package + +on: + push: + branches: + - feature + - feature/** + - develop + - main + tags: + - v*.*.* + + pull_request: + branches: + - feature + - feature/** + - develop + - main + +permissions: + contents: read + +jobs: + ci: + if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/heads/') + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + - "3.12" + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install tooling + run: | + pip install hatch ruff + + - name: Ruff Check + run: ruff 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 + + - 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 39f7777..0000000 --- a/.github/workflows/publish-pypi.yml +++ /dev/null @@ -1,72 +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 Poetry - run: | - curl -sSL https://install.python-poetry.org | python3 - - echo "$HOME/.local/bin" >> $GITHUB_PATH - - - name: Configure Poetry - run: | - poetry config virtualenvs.create true - poetry config virtualenvs.in-project true - - - name: Update version (if manual trigger) - if: github.event_name == 'workflow_dispatch' - run: | - poetry version ${{ inputs.version }} - 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: Install dependencies - run: poetry install --no-interaction - - - name: Build package - run: poetry 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/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fe6d30a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,95 @@ +# 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.8] - 2026-04-29 + +### Fixed + +- Avoid inserting the user name twice in the `tmpUrl` variable of the `main` section + +## [v0.1.7] - 2026-04-17 + +### Fixed + +- Fix typo introduced in v0.1.6 + +## [v0.1.6] - 2026-04-17 + +### Changed + +- Added support for generating a minimal STAC catalog for `File` outputs. + +## [v0.1.5] - 2026-01-21 + +### Changed + +- Adopted PEP 621 standard for project metadata in `pyproject.toml` + +### Removed + +- Removed unnecessary `__init__.py` file for cleaner package structure + +## [v0.1.4] - 2026-01-19 + +### Changed + +- `CommonExecutionHandler` now inherits from `zoo_runner_common.handlers.ExecutionHandler` + +## [v0.1.3] - 2026-01-19 + +### Fixed + +- Fixed import to enable use of `botocore.session` + +## [v0.1.2] - 2026-01-19 + +### Changed + +- Updated `zoo-runner-common` dependency from `>=0.1.0` to `>=0.1.2` to support the new package structure and import paths (`from zoo_runner_common import ...`) + +## [v0.1.1] - 2026-01-18 + +### Added + +- Package is now officially available on PyPI: `pip install zoo-template-common` +- Automated CI/CD pipeline for PyPI publication on new releases +- Added `zoo-runner-common` (>=0.1.0) as an official dependency +- Complete PyPI metadata: classifiers, keywords, and project URLs +- Enhanced documentation with real-world examples + +### Changed + +- License updated to Apache-2.0 +- Minimum Python version raised to 3.10 (supports 3.10, 3.11, 3.12) +- Updated documentation to reflect latest service template patterns + +### Fixed +- Multiple typos in codebase and documentation +- Package structure improvements for better imports + +## [v0.1.0] - 2026-01-18 + +### Added + +- Initial release of `zoo-template-common` +- `CommonExecutionHandler`: extensible base class for CWL workflow execution with pre/post-execution hooks, automatic STAC catalog output processing, Kubernetes pod environment variable and node selector management, secrets handling, and tool log integration +- `CustomStacIO`: STAC I/O class with S3 support via `boto3` for reading and writing STAC catalogs from/to S3 or local file systems +- MkDocs Material documentation site +- Core dependencies: `loguru>=0.7.0`, `pystac>=1.8.0`, `pyyaml>=6.0`, `boto3>=1.28.0`, `botocore>=1.31.0` + +[Unreleased]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.8...HEAD +[v0.1.8]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.7...v0.1.8 +[v0.1.7]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.6...v0.1.7 +[v0.1.6]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.5...v0.1.6 +[v0.1.5]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.4...v0.1.5 +[v0.1.4]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.3...v0.1.4 +[v0.1.3]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.2...v0.1.3 +[v0.1.2]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.1...v0.1.2 +[v0.1.1]: https://github.com/ZOO-Project/zoo-template-common/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/ZOO-Project/zoo-template-common/releases/tag/v0.1.0 \ No newline at end of file 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 index 92a7b47..accb0d6 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,48 +1,56 @@ # Contributing to zoo-template-common -Thank you for your interest in contributing to zoo-template-common! This document provides guidelines for contributing to the project. +Thank you for your interest in contributing to `zoo-template-common`! This document provides guidelines for contributing to the project. + +## Overview + +`zoo-template-common` provides shared utilities and base service templates for ZOO-Project CWL runners, including `CommonExecutionHandler` and `CustomStacIO`. 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.8 or higher +- Python 3.10 or higher - Git -- Poetry (recommended) or pip +- [Hatch](https://hatch.pypa.io/latest/install/) (`pip install hatch`) ### Development Setup 1. **Fork and Clone** -```bash -git clone https://github.com/YOUR-USERNAME/zoo-template-common.git -cd zoo-template-common -``` + ```bash + git clone https://github.com/ZOO-Project/zoo-template-common.git + cd zoo-template-common + ``` -2. **Create Virtual Environment** +2. **Install Hatch** -```bash -python -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate -``` + ```bash + pip install hatch + ``` -3. **Install Dependencies** +3. **Enter the Default Development Environment** -With Poetry: -```bash -poetry install -``` + Hatch automatically creates and manages a virtual environment for you: -Or with pip: -```bash -pip install -e ".[dev]" -``` + ```bash + hatch shell + ``` + + This installs all dependencies defined under `[tool.hatch.envs.default]` in `pyproject.toml`. 4. **Verify Installation** -```bash -python -c "from zoo_template_common import CommonExecutionHandler; print('OK')" -``` + ```bash + python -c "from zoo_template_common import CommonExecutionHandler; print('OK')" + ``` + +--- ## Development Workflow @@ -52,151 +60,119 @@ python -c "from zoo_template_common import CommonExecutionHandler; print('OK')" git checkout -b feature/your-feature-name ``` -Use prefixes: -- `feature/` - New features -- `fix/` - Bug fixes -- `docs/` - Documentation updates -- `refactor/` - Code refactoring +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: +Follow the coding standards described in the [Code Standards](#code-standards) section below. -- **PEP 8**: Follow Python style guide -- **Type Hints**: Use type annotations -- **Docstrings**: Google-style docstrings -- **Comments**: Explain complex logic +### 3. Run Tests -**Example:** +The project uses `pytest` for testing via a dedicated Hatch environment: -```python -def get_pod_env_vars(self) -> dict: - """ - Get environment variables for pod execution. - - Returns: - dict: Environment variables to set in the execution pod. - Keys are variable names, values are variable values. - - Example: - >>> handler = CommonExecutionHandler(conf) - >>> env = handler.get_pod_env_vars() - >>> print(env["MY_VAR"]) - 'value' - """ - return {} -``` +```bash +# Run tests +hatch run test:test -### 3. Write Tests +# Run tests quietly +hatch run test:test-q +``` -Add tests for new features: +### 4. Update Documentation -```python -# tests/test_my_feature.py -import pytest -from zoo_template_common import CommonExecutionHandler +- Update docstrings in the source code +- Update relevant `.md` files in `docs/`, if present -def test_my_feature(): - """Test my new feature""" - conf = {"lenv": {"message": ""}} - handler = CommonExecutionHandler(conf) - - # Test your feature - result = handler.my_feature() - assert result == expected_value -``` +### 5. Commit Changes -Run tests: +Write clear, conventional commit messages: ```bash -pytest tests/ +git add . +git commit -m "feat: add support for custom STAC catalog handling" ``` -### 4. Update Documentation +Commit types: -- Update docstrings -- Update relevant `.md` files in `docs/` -- Add examples if applicable +- `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 -### 5. Commit Changes - -Write clear commit messages: +### 6. Push and Create a Pull Request ```bash -git add . -git commit -m "Add feature: description of feature" +git push origin feature/your-feature-name ``` -Good commit message format: -``` -: +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`. -Types: -- `feat`: New feature -- `fix`: Bug fix -- `docs`: Documentation -- `style`: Formatting -- `refactor`: Code restructuring -- `test`: Adding tests -- `chore`: Maintenance +--- -### 6. Push and Create PR +## Hatch Environments -```bash -git push origin feature/your-feature-name -``` +The project defines two Hatch environments in `pyproject.toml`: -Create a Pull Request on GitHub with: -- Clear title and description -- Reference related issues -- List changes made -- Add screenshots if UI-related +| Environment | Purpose | Key Command | +|---|---|---| +| `default` | Day-to-day development | `hatch shell` | +| `test` | Running tests | `hatch run test:test` | + +--- ## Code Standards ### Python Style -```python -# Good -def get_secrets(self) -> dict: - """Load secrets from configuration files.""" - secrets = {} - for path in self.SECRET_PATHS: - if os.path.exists(path): - secrets.update(self.local_get_file(path)) - return secrets - -# Bad -def get_secrets(self): - s = {} - for p in ["/var/etc/secrets/processing_secrets.yaml"]: - if os.path.exists(p): - s.update(self.local_get_file(p)) - return s -``` +- 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** +- Code is formatted with [`black`](https://black.readthedocs.io/) (line length 100) and import-sorted with [`isort`](https://pycqa.github.io/isort/) (black profile) — run these before committing if your editor doesn't do it automatically +- `pylint` configuration enforces a max line length of 100 -### Type Hints +**Good example:** ```python -from typing import Dict, List, Optional +def get_stac_catalog(self, location: str) -> dict: + """ + Load a STAC catalog from a local or S3 location. -def process_items(self, items: List[str]) -> Dict[str, int]: - """Process list of items.""" - pass + Args: + location: Path or S3 URI to the STAC catalog. -def get_config(self) -> Optional[Dict[str, any]]: - """Get configuration if available.""" - pass + Returns: + A dictionary representing the parsed STAC catalog. + + Raises: + ValueError: If location is empty or invalid. + """ + if not location: + raise ValueError("location must not be empty") + ... ``` ### Error Handling -```python +````python # Good def load_file(self, path: str) -> dict: """Load YAML file.""" @@ -214,144 +190,58 @@ def load_file(self, path: str) -> dict: def load_file(self, path): with open(path) as f: return yaml.safe_load(f) -``` +```` -## Testing Guidelines +### Versioning -### Unit Tests +The package version is managed in `zoo_template_common/__about__.py`. Do **not** manually edit the version; it is updated as part of the release process. -Test individual methods: +--- -```python -def test_get_pod_env_vars(): - """Test pod environment variables""" - conf = {"lenv": {}} - handler = CommonExecutionHandler(conf) - - env = handler.get_pod_env_vars() - - assert isinstance(env, dict) - assert "CUSTOM_VAR" not in env # Base handler returns empty -``` +## Testing Guidelines -### Integration Tests +### Unit Tests -Test interactions: +Place unit tests under `tests/` and name files `test_*.py`. Test discovery is configured via `[tool.pytest.ini_options]` (`testpaths = ["tests"]`): ```python -def test_handler_lifecycle(): - """Test complete handler lifecycle""" - conf = {"lenv": {"message": ""}} - outputs = {} - - handler = CommonExecutionHandler(conf, outputs) - - # Pre-execution - handler.pre_execution_hook() - - # Execution simulation - outputs["stac"] = "/tmp/catalog.json" - - # Post-execution - handler.post_execution_hook(None, outputs, None, None) - - assert "message" in conf["lenv"] -``` - -### Test Coverage - -Aim for >80% code coverage: - -```bash -pytest --cov=zoo_template_common tests/ -``` - -## Documentation - -### Docstring Format +# tests/test_handlers.py +import unittest +from zoo_template_common import CommonExecutionHandler -Use Google-style docstrings: +class TestCommonExecutionHandler(unittest.TestCase): -```python -def my_method(self, param1: str, param2: int = 0) -> bool: - """ - Short description of method. - - Longer description with more details about what the method does, - how it works, and any important notes. - - Args: - param1: Description of param1 - param2: Description of param2. Defaults to 0. - - Returns: - True if successful, False otherwise. - - Raises: - ValueError: If param1 is empty - IOError: If file cannot be read - - Example: - >>> handler = MyHandler(conf) - >>> result = handler.my_method("test", 5) - >>> print(result) - True - """ - pass + def test_initialization(self): + """Test that the handler initializes correctly.""" + conf = {"lenv": {"message": ""}} + handler = CommonExecutionHandler(conf=conf) + self.assertIsNotNone(handler) ``` -### Update Documentation - -When adding features: - -1. Update API reference in `docs/api-reference/` -2. Add usage examples to `docs/user-guide/` -3. Update `README.md` if needed -4. Add to changelog +--- ## Release Process -1. Update version in `pyproject.toml` -2. Update `CHANGELOG.md` -3. Create release tag -4. Build and publish to PyPI - -```bash -# Update version -poetry version patch # or minor, major +Releases are managed by project maintainers: -# Build -poetry build +1. Ensure all changes are merged into `develop` and tested +2. Update the version in `zoo_template_common/__about__.py` +3. Update `CHANGELOG.md` +4. Create a new release on GitHub with the tag targeting the `develop` branch +5. Once the release is published, merge `develop` into `main` -# Publish (maintainers only) -poetry publish -``` +--- ## Getting Help -- **Issues**: Open an issue on GitHub -- **Discussions**: Use GitHub Discussions -- **Contact**: Email the maintainers - -## Code of Conduct - -Be respectful and professional: +- **Bug reports / feature requests**: [Open an issue](https://github.com/ZOO-Project/zoo-template-common/issues) -- Be welcoming and inclusive -- Be respectful of differing viewpoints -- Accept constructive criticism gracefully -- Focus on what is best for the community +--- ## License -By contributing, you agree that your contributions will be licensed under the same license as the project. - -## Questions? - -If you have questions about contributing, feel free to: +By contributing, you agree that your contributions will be licensed under the **Apache License 2.0**, the same license as this project. -- Open an issue -- Start a discussion -- Contact the maintainers +--- Thank you for contributing! 🎉 diff --git a/pyproject.toml b/pyproject.toml index 4fb65e8..90e0f0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,23 @@ -[tool.poetry] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] name = "zoo-template-common" -version = "0.1.8" +dynamic = ["version"] description = "Shared utilities and base service templates for ZOO CWL runners" authors = [ - "Aryan Khare ", - "Gérald Fenoy " + { name = "Aryan Khare", email = "kharearyan78@gmail.com" }, + { name = "Gérald Fenoy", email = "gerald.fenoy@geolabs.fr" } ] readme = "README.md" +requires-python = ">=3.10" packages = [{ include = "zoo_template_common" }] license = "Apache-2.0" homepage = "https://github.com/ZOO-Project/zoo-template-common" repository = "https://github.com/ZOO-Project/zoo-template-common" +Issues = "https://github.com/ZOO-Project/zoo-template-common/issues" +Changelog = "https://github.com/ZOO-Project/zoo-template-common/blob/main/CHANGELOG.md" documentation = "https://zoo-project.github.io/zoo-template-common/" keywords = ["zoo-project", "cwl", "template", "eoepca", "stac", "workflow"] classifiers = [ @@ -23,18 +30,50 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] -[tool.poetry.dependencies] -python = ">=3.10" -zoo-runner-common = ">=0.1.3" -loguru = ">=0.7.0" -pystac = ">=1.8.0" -pyyaml = ">=6.0" -boto3 = ">=1.28.0" -botocore = ">=1.31.0" -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +dependencies = [ + "zoo-runner-common>=0.1.3", + "loguru>=0.7.0", + "pystac>=1.8.0", + "PyYAML>=6.0", + "boto3>=1.28.0", + "botocore>=1.31.0", + 'attrs', + "cwl-utils>=0.14", +] + + +[tool.hatch.version] +path = "zoo_template_common/__about__.py" + + +[tool.hatch.envs.default] +dependencies = [ + "zoo-runner-common>=0.1.3", + "loguru>=0.7.0", + "pystac>=1.8.0", + "PyYAML>=6.0", + "boto3>=1.28.0", + "botocore>=1.31.0", + "attrs", + "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}" + + +[tool.pytest.ini_options] +testpaths = ["tests"] [tool.black] line-length = 100 @@ -45,3 +84,4 @@ profile = "black" [tool.pylint.'MESSAGES CONTROL'] max-line-length = 100 + diff --git a/tests/test_custom_stac_io.py b/tests/test_custom_stac_io.py new file mode 100644 index 0000000..257c9c3 --- /dev/null +++ b/tests/test_custom_stac_io.py @@ -0,0 +1,60 @@ +from unittest.mock import MagicMock, patch + +import pytest +from pystac.stac_io import DefaultStacIO + + +@pytest.fixture +def stac_io(): + with patch("botocore.session.Session") as mock_session_cls: + mock_client = MagicMock() + mock_session_cls.return_value.create_client.return_value = mock_client + from zoo_template_common.custom_stac_io import CustomStacIO + + yield CustomStacIO() + + +def test_read_text_s3_url_calls_s3_client(stac_io): + stac_io.s3_client.get_object.return_value = { + "Body": MagicMock(read=lambda: b"stac content") + } + result = stac_io.read_text("s3://my-bucket/path/to/file.json") + stac_io.s3_client.get_object.assert_called_once_with( + Bucket="my-bucket", Key="path/to/file.json" + ) + assert result == "stac content" + + +def test_read_text_non_s3_url_delegates_to_default(stac_io): + with patch.object(DefaultStacIO, "read_text", return_value="local content"): + result = stac_io.read_text("http://example.com/stac.json") + assert result == "local content" + + +def test_write_text_s3_url_calls_put_object(stac_io): + stac_io.write_text( + "s3://my-bucket/path/output.json", '{"type": "FeatureCollection"}' + ) + stac_io.s3_client.put_object.assert_called_once_with( + Body=b'{"type": "FeatureCollection"}', + Bucket="my-bucket", + Key="path/output.json", + ContentType="application/geo+json", + ) + + +def test_write_text_non_s3_url_delegates_to_default(stac_io): + with patch.object(DefaultStacIO, "write_text") as mock_write: + stac_io.write_text("file:///tmp/output.json", "content") + mock_write.assert_called_once() + + +def test_s3_key_strips_leading_slash(stac_io): + stac_io.s3_client.get_object.return_value = { + "Body": MagicMock(read=lambda: b"data") + } + stac_io.read_text("s3://bucket/prefix/file.json") + call_kwargs = stac_io.s3_client.get_object.call_args + assert call_kwargs.kwargs["Key"] == "prefix/file.json" + # Key must not start with '/' + assert not call_kwargs.kwargs["Key"].startswith("/") 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 diff --git a/zoo_template_common/__about__.py b/zoo_template_common/__about__.py new file mode 100644 index 0000000..9cb17e7 --- /dev/null +++ b/zoo_template_common/__about__.py @@ -0,0 +1 @@ +__version__ = "0.1.8" diff --git a/zoo_template_common/__init__.py b/zoo_template_common/__init__.py index 8e65055..77bee3c 100644 --- a/zoo_template_common/__init__.py +++ b/zoo_template_common/__init__.py @@ -6,6 +6,7 @@ try: from importlib.metadata import version + __version__ = version("zoo-template-common") except Exception: __version__ = "unknown" diff --git a/zoo_template_common/common_execution_handler.py b/zoo_template_common/common_execution_handler.py index 402b574..663508f 100644 --- a/zoo_template_common/common_execution_handler.py +++ b/zoo_template_common/common_execution_handler.py @@ -16,8 +16,10 @@ import zoo except ImportError: from zoo_runner_common.zoostub import ZooStub + zoo = ZooStub() + class CommonExecutionHandler(ExecutionHandler): """Simple execution handler for ZOO-Project CWL workflows. @@ -66,11 +68,14 @@ def setOutput(self, outputName, values): datetime=datetime.now(tz=timezone.utc), properties={}, ) - item.add_asset("data", Asset( - href=str(value_uri), - media_type=mime_type, - roles=["data"], - )) + item.add_asset( + "data", + Asset( + href=str(value_uri), + media_type=mime_type, + roles=["data"], + ), + ) item.collection_id = collection_id items.append(item) continue