|
| 1 | +# Development and Contributions |
| 2 | + |
| 3 | +The [RO-Crate Validation Service](https://github.com/eScienceLab/RO-Crate-Validation-Service) is an open source project and welcomes contributions of all kinds: bug reports, code or documentation changes, and reviews of proposed changes. The underlying [rocrate-validator tool](https://github.com/crs4/rocrate-validator) is also open source, and is a separate project maintained by CRS4. |
| 4 | + |
| 5 | +This service is written with Python 3.11, built on Flask/APIFlask and Celery, and wraps the [`rocrate-validator`](https://rocrate-validator.readthedocs.io/) in a REST API. The RO-Crate Validation Service enables pipelines, other services, and Trusted Research Environments (TREs) to validate an RO-Crate over HTTP without running the validator themselves. |
| 6 | + |
| 7 | +## Contributing |
| 8 | + |
| 9 | +The easiest way to start contributing is to create an issue, either to let us know of a bug or error, or to propose a piece of work you want to do. For the RO-Crate Validation Service (the API service, Docker image, and Compose stack) use the [RO-Crate Validation Service issues](https://github.com/eScienceLab/RO-Crate-Validation-Service/issues) page. Issues with the validation checks themselves belong to the underlying tool rather than this service: report those on the [rocrate-validator issues](https://github.com/crs4/rocrate-validator/issues) page, and follow that project's own contribution guidance. |
| 10 | + |
| 11 | +### Code contributions |
| 12 | + |
| 13 | +If you want to contribute code changes via GitHub then you may want to read ['How to Contribute to an Open Source Project on GitHub'](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). We use [GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) to manage changes: |
| 14 | + |
| 15 | +1. Create a new branch in your local clone of this repository for each significant change. |
| 16 | +2. Commit the change in that branch. |
| 17 | +3. Push that branch to your fork of this repository on GitHub. |
| 18 | +4. Submit a pull request from that branch to the [upstream repository](https://github.com/eScienceLab/RO-Crate-Validation-Service). |
| 19 | +5. If you receive feedback, make the changes in your local clone and push to your branch on GitHub: the pull request will update automatically. |
| 20 | + |
| 21 | +!!! warning |
| 22 | + Note that we use the `develop` branch for development work, and this is where your PR should be aimed. The `main` branch is used for releases, and only pull requests from the `develop` branch are accepted to this. |
| 23 | + |
| 24 | +## Development stack |
| 25 | + |
| 26 | +The development Compose file builds the image from the local `Dockerfile` and mounts the repository's test profiles into both the API and worker containers: |
| 27 | + |
| 28 | +```bash |
| 29 | +docker compose -f docker-compose-develop.yml up --build |
| 30 | +``` |
| 31 | + |
| 32 | +Here `--build` matters: without it, Compose reuses the previously built image and local code changes are not picked up. Add `--profile objectstore` to start the bundled RustFS store for storage-backed work; configuration is the same as in [Installation & Setup](installation.md#configuration-reference). |
| 33 | + |
| 34 | +## Tests |
| 35 | + |
| 36 | +Install the development dependencies, then run the unit tests, which do not use Docker Engine: |
| 37 | + |
| 38 | +```bash |
| 39 | +pip install -r requirements-dev.txt |
| 40 | +``` |
| 41 | + |
| 42 | +```bash |
| 43 | +pytest --ignore=tests/test_integration.py |
| 44 | +``` |
| 45 | + |
| 46 | +The integration tests bring up the full Compose stack (including the object store) and seed crates with `boto3`, for which they need Docker Engine to be running: |
| 47 | + |
| 48 | +```bash |
| 49 | +pytest tests/test_integration.py |
| 50 | +``` |
| 51 | + |
| 52 | +`tests/` mirrors the layout of the `app/` package, so the tests for a module are in the matching directory. |
| 53 | + |
| 54 | +## Linting |
| 55 | + |
| 56 | +The project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting, configured in `pyproject.toml`: |
| 57 | + |
| 58 | +```bash |
| 59 | +ruff check . && ruff format --check . |
| 60 | +``` |
| 61 | + |
| 62 | +## Dependencies |
| 63 | + |
| 64 | +Direct dependencies are declared in `pyproject.toml`; while the `requirements*.txt` files are locks generated using `pip-compile`: |
| 65 | + |
| 66 | +```bash |
| 67 | +pip-compile pyproject.toml -o requirements.txt |
| 68 | +``` |
| 69 | + |
| 70 | +```bash |
| 71 | +pip-compile --extra dev pyproject.toml -o requirements-dev.txt |
| 72 | +``` |
| 73 | + |
| 74 | +## Continuous Integration |
| 75 | + |
| 76 | +Pull requests to `develop` will trigger three workflows: unit tests, integration tests (which start the Compose stack), and lint (`ruff check` and `ruff format --check`). |
| 77 | + |
| 78 | +## How the API works |
| 79 | + |
| 80 | +The API server handles HTTP and runs metadata-only validation inline. Object storage-backed validation is queued through Redis to a Celery worker, which reads the crate from the S3-compatible store, validates it, and writes the result back: |
| 81 | + |
| 82 | +```mermaid |
| 83 | +flowchart LR |
| 84 | + Client([Client]) |
| 85 | + API["Flask API"] |
| 86 | + Broker[("Redis")] |
| 87 | + Worker["Celery worker"] |
| 88 | + Validator["rocrate-validator"] |
| 89 | + Store[("S3-compatible store")] |
| 90 | +
|
| 91 | + Client --> API |
| 92 | + API -->|metadata-only: inline| Validator |
| 93 | + API --> Broker --> Worker --> Validator |
| 94 | + Worker <--> Store |
| 95 | +``` |
| 96 | + |
| 97 | +The worker runs its stages strictly in order: fetch, validate, persist, webhook; so a storage write failure can never be followed by a success notification, and every outcome (including `error` outcomes) is persisted so a later `GET` reflects what happened. |
| 98 | + |
| 99 | +## Project structure |
| 100 | + |
| 101 | +``` |
| 102 | +app/ |
| 103 | +├── __init__.py # app factory: config, blueprints, error handlers, request IDs |
| 104 | +├── health.py # /healthz and /readyz |
| 105 | +├── storage/ # object-storage abstraction |
| 106 | +│ ├── base.py # StorageBackend protocol |
| 107 | +│ ├── s3.py # boto3 implementation (any S3-compatible store) |
| 108 | +│ ├── memory.py # in-memory backend (tests / local) |
| 109 | +│ └── errors.py # StorageError, ObjectNotFound |
| 110 | +├── crates/ # crate identity, layout, resolution |
| 111 | +│ ├── ids.py # Crate ID validation |
| 112 | +│ ├── layout.py # object keys |
| 113 | +│ └── resolver.py # deterministic zip/directory resolution |
| 114 | +├── validation/ # validation boundary |
| 115 | +│ ├── results.py # ValidationOutcome (valid/invalid/error) |
| 116 | +│ └── runner.py # wraps rocrate-validator |
| 117 | +├── ro_crates/routes/ # HTTP endpoints (metadata + ID-based) |
| 118 | +├── services/ # request handling and logging |
| 119 | +├── tasks/validation_tasks.py # Celery task: fetch, validate, persist, webhook |
| 120 | +└── utils/ # validated settings, webhook delivery |
| 121 | +``` |
0 commit comments