From 1b25139a85585d53cc47e1aed2f9e8e8f1b36aa5 Mon Sep 17 00:00:00 2001 From: Baudouin Raoult Date: Thu, 10 Oct 2024 07:21:30 +0100 Subject: [PATCH] first commit --- .gitattributes | 1 + .github/CODEOWNERS | 6 + .github/ci-hpc-config.yml | 8 + .github/workflows/changelog-pr-update.yml | 18 ++ .../workflows/changelog-release-update.yml | 35 +++ .github/workflows/ci.yml | 43 ++++ .github/workflows/label-public-pr.yml | 10 + .github/workflows/python-publish.yml | 27 +++ .github/workflows/python-pull-request.yml | 23 ++ .github/workflows/readthedocs-pr-update.yml | 22 ++ .gitignore | 189 ++++++++++++++++ .pre-commit-config.yaml | 87 ++++++++ .readthedocs.yaml | 16 ++ CHANGELOG.md | 9 + LICENSE | 201 ++++++++++++++++++ README.md | 42 ++++ docs/Makefile | 22 ++ docs/_static/logo.png | Bin 0 -> 17128 bytes docs/_static/style.css | 48 +++++ docs/_templates/.gitkeep | 0 docs/conf.py | 126 +++++++++++ docs/index.rst | 62 ++++++ docs/installing.rst | 31 +++ docs/modules/placeholder.rst | 8 + pyproject.toml | 71 +++++++ src/anemoi/transform/__init__.py | 9 + src/anemoi/transform/__main__.py | 28 +++ src/anemoi/transform/commands/__init__.py | 24 +++ src/anemoi/transform/commands/hello.py | 27 +++ src/anemoi/transform/placeholder.py | 9 + tests/test_transform.py | 17 ++ 31 files changed, 1219 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/CODEOWNERS create mode 100644 .github/ci-hpc-config.yml create mode 100644 .github/workflows/changelog-pr-update.yml create mode 100644 .github/workflows/changelog-release-update.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/label-public-pr.yml create mode 100644 .github/workflows/python-publish.yml create mode 100644 .github/workflows/python-pull-request.yml create mode 100644 .github/workflows/readthedocs-pr-update.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yaml create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/Makefile create mode 100644 docs/_static/logo.png create mode 100644 docs/_static/style.css create mode 100644 docs/_templates/.gitkeep create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/installing.rst create mode 100644 docs/modules/placeholder.rst create mode 100644 pyproject.toml create mode 100644 src/anemoi/transform/__init__.py create mode 100644 src/anemoi/transform/__main__.py create mode 100644 src/anemoi/transform/commands/__init__.py create mode 100644 src/anemoi/transform/commands/hello.py create mode 100644 src/anemoi/transform/placeholder.py create mode 100644 tests/test_transform.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a19ade0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGELOG.md merge=union diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0211a4e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# CODEOWNERS file + +# Protect workflow files +/.github/ @theissenhelen @jesperdramsch @gmertes @b8raoult @floriankrb +/.pre-commit-config.yaml @theissenhelen @jesperdramsch @gmertes @b8raoult @floriankrb +/pyproject.toml @theissenhelen @jesperdramsch @gmertes @b8raoult @floriankrb diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml new file mode 100644 index 0000000..cd11ba2 --- /dev/null +++ b/.github/ci-hpc-config.yml @@ -0,0 +1,8 @@ +build: + python: '3.10' + modules: + - ninja + parallel: 64 + + pytest_cmd: | + python -m pytest -vv -m 'not notebook and not no_cache_init' --cov=. --cov-report=xml diff --git a/.github/workflows/changelog-pr-update.yml b/.github/workflows/changelog-pr-update.yml new file mode 100644 index 0000000..73cb1eb --- /dev/null +++ b/.github/workflows/changelog-pr-update.yml @@ -0,0 +1,18 @@ +name: Check Changelog Update on PR +on: + pull_request: + types: [assigned, opened, synchronize, reopened, labeled, unlabeled] + branches: + - main + - develop + paths-ignore: + - .pre-commit-config.yaml + - .readthedocs.yaml +jobs: + Check-Changelog: + name: Check Changelog Action + runs-on: ubuntu-20.04 + steps: + - uses: tarides/changelog-check-action@v2 + with: + changelog: CHANGELOG.md diff --git a/.github/workflows/changelog-release-update.yml b/.github/workflows/changelog-release-update.yml new file mode 100644 index 0000000..17d9525 --- /dev/null +++ b/.github/workflows/changelog-release-update.yml @@ -0,0 +1,35 @@ +# .github/workflows/update-changelog.yaml +name: "Update Changelog" + +on: + release: + types: [released] + workflow_dispatch: ~ + +permissions: + pull-requests: write + contents: write + +jobs: + update: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.target_commitish }} + + - name: Update Changelog + uses: stefanzweifel/changelog-updater-action@v1 + with: + latest-version: ${{ github.event.release.tag_name }} + heading-text: ${{ github.event.release.name }} + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + branch: docs/changelog-update-${{ github.event.release.tag_name }} + title: '[Changelog] Update to ${{ github.event.release.tag_name }}' + add-paths: | + CHANGELOG.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..66bb35f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: ci + +on: + # Trigger the workflow on push to master or develop, except tag creation + push: + branches: + - 'main' + - 'develop' + tags-ignore: + - '**' + paths: + - "src/**" + - "tests/**" + + # Trigger the workflow on pull request + pull_request: ~ + + # Trigger the workflow manually installs + workflow_dispatch: ~ + + # Trigger after public PR approved for CI + pull_request_target: + types: [labeled] + +jobs: + # Run CI including downstream packages on self-hosted runners + downstream-ci: + name: downstream-ci + if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} + uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci.yml@main + with: + anemoi-transform: ecmwf/anemoi-transform@${{ github.event.pull_request.head.sha || github.sha }} + codecov_upload: true + secrets: inherit + + # Build downstream packages on HPC + downstream-ci-hpc: + name: downstream-ci-hpc + if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} + uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci-hpc.yml@main + with: + anemoi-transform: ecmwf/anemoi-transform@${{ github.event.pull_request.head.sha || github.sha }} + secrets: inherit diff --git a/.github/workflows/label-public-pr.yml b/.github/workflows/label-public-pr.yml new file mode 100644 index 0000000..59b2bfa --- /dev/null +++ b/.github/workflows/label-public-pr.yml @@ -0,0 +1,10 @@ +# Manage labels of pull requests that originate from forks +name: label-public-pr + +on: + pull_request_target: + types: [opened, synchronize] + +jobs: + label: + uses: ecmwf-actions/reusable-workflows/.github/workflows/label-pr.yml@v2 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..2cb554a --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,27 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + quality: + uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-precommit-run.yml@v2 + with: + skip-hooks: "no-commit-to-branch" + + checks: + strategy: + matrix: + python-version: ["3.9", "3.10"] + uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-pytest-pyproject.yml@v2 + with: + python-version: ${{ matrix.python-version }} + + deploy: + needs: [checks, quality] + uses: ecmwf-actions/reusable-workflows/.github/workflows/cd-pypi.yml@v2 + secrets: inherit diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml new file mode 100644 index 0000000..2185264 --- /dev/null +++ b/.github/workflows/python-pull-request.yml @@ -0,0 +1,23 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Code Quality checks for PRs + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + quality: + uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-precommit-run.yml@v2 + with: + skip-hooks: "no-commit-to-branch" + + checks: + strategy: + matrix: + python-version: ["3.9", "3.10"] + uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-pytest-pyproject.yml@v2 + with: + python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/readthedocs-pr-update.yml b/.github/workflows/readthedocs-pr-update.yml new file mode 100644 index 0000000..dfdd4f5 --- /dev/null +++ b/.github/workflows/readthedocs-pr-update.yml @@ -0,0 +1,22 @@ +name: Read the Docs PR Preview +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + # Execute this action only on PRs that touch + # documentation files. + paths: + - "docs/**" + +permissions: + pull-requests: write + +jobs: + documentation-links: + runs-on: ubuntu-latest + steps: + - uses: readthedocs/actions/preview@v1 + with: + project-slug: "anemoi-transform" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4a8551 --- /dev/null +++ b/.gitignore @@ -0,0 +1,189 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +*.grib +*.onnx +*.ckpt +*.swp +*.npy +*.download +? +?.* +foo +bar +*.grib +*.nc +*.npz +*.json +*.zarr/ +~$images.pptx +test.py +cutout.png +*.out + +_build/ +? +?.* +~* +*.sync +_version.py +*.code-workspace diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..cbb03cb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,87 @@ +repos: +# Empty notebookds +- repo: local + hooks: + - id: clear-notebooks-output + name: clear-notebooks-output + files: tools/.*\.ipynb$ + stages: [commit] + language: python + entry: jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace + additional_dependencies: [jupyter] +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml # Check YAML files for syntax errors only + args: [--unsafe, --allow-multiple-documents] + - id: debug-statements # Check for debugger imports and py37+ breakpoint() + - id: end-of-file-fixer # Ensure files end in a newline + - id: trailing-whitespace # Trailing whitespace checker + - id: no-commit-to-branch # Prevent committing to main / master + - id: check-added-large-files # Check for large files added to git + - id: check-merge-conflict # Check for files that contain merge conflict +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 # Use the ref you want to point at + hooks: + - id: python-use-type-annotations # Check for missing type annotations + - id: python-check-blanket-noqa # Check for # noqa: all + - id: python-no-log-warn # Check for log.warn +- repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 + hooks: + - id: black + args: [--line-length=120] +- repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: + - -l 120 + - --force-single-line-imports + - --profile black +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.4 + hooks: + - id: ruff + # Next line if for documenation cod snippets + exclude: '.*/[^_].*_\.py$' + args: + - --line-length=120 + - --fix + - --exit-non-zero-on-fix + - --preview +- repo: https://github.com/sphinx-contrib/sphinx-lint + rev: v1.0.0 + hooks: + - id: sphinx-lint +# For now, we use it. But it does not support a lot of sphinx features +- repo: https://github.com/dzhu/rstfmt + rev: v0.0.14 + hooks: + - id: rstfmt + exclude: 'cli/.*' # Because we use argparse +- repo: https://github.com/b8raoult/pre-commit-docconvert + rev: "0.1.5" + hooks: + - id: docconvert + args: ["numpy"] +- repo: https://github.com/tox-dev/pyproject-fmt + rev: "2.2.3" + hooks: + - id: pyproject-fmt +- repo: https://github.com/jshwi/docsig # Check docstrings against function sig + rev: v0.60.1 + hooks: + - id: docsig + args: + - --ignore-no-params # Allow docstrings without parameters + - --check-dunders # Check dunder methods + - --check-overridden # Check overridden methods + - --check-protected # Check protected methods + - --check-class # Check class docstrings + - --disable=E113 # Disable empty docstrings + - --summary # Print a summary +ci: + autoupdate_schedule: monthly +ci: + autoupdate_schedule: monthly diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..06c8ab4 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..063481c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Please add your functional changes to the appropriate section in the PR. +Keep it human-readable, your future self will thank you! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2aaccba --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# anemoi-transform + +**DISCLAIMER** +This project is **BETA** and will be **Experimental** for the foreseeable future. +Interfaces and functionality are likely to change, and the project itself may be scrapped. +**DO NOT** use this software in any project/software that is operational. + +Miscellanous data transformation functions for training data-driven weather forecasts. + +## Documentation + +The documentation can be found at https://anemoi-transform.readthedocs.io/. + +## Install + +Install via `pip` with: + +``` +$ pip install anemoi-transform +``` + +## License + +``` +Copyright 2022, European Centre for Medium Range Weather Forecasts. + +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. + +In applying this licence, ECMWF does not waive the privileges and immunities +granted to it by virtue of its status as an intergovernmental organisation +nor does it submit to any jurisdiction. +``` diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..6c0762a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,22 @@ +#!/usr/bin/env make -f + +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f78572e73c02e791661a509be50fb9e90f0d3a56 GIT binary patch literal 17128 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 T1l9fP=$w?8ylAbkLD2sLIU~(~ literal 0 HcmV?d00001 diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 0000000..9a2b3af --- /dev/null +++ b/docs/_static/style.css @@ -0,0 +1,48 @@ +.wy-side-nav-search { + background-color: #f7f7f7; +} + +/*There is a clash between xarray notebook styles and readthedoc*/ + +.rst-content dl.xr-attrs dt { + all: revert; + font-size: 95%; + white-space: nowrap; +} + +.rst-content dl.xr-attrs dd { + font-size: 95%; +} + +.xr-wrap { + font-size: 85%; +} + +.wy-table-responsive table td, .wy-table-responsive table th { + white-space: inherit; +} + +/* +.wy-table-responsive table td, +.wy-table-responsive table th { + white-space: normal !important; + vertical-align: top !important; +} + +.wy-table-responsive { + margin-bottom: 24px; + max-width: 100%; + overflow: visible; +} */ + +/* Hide notebooks warnings */ +.nboutput .stderr { + display: none; +} + +/* +Set logo size +*/ +.wy-side-nav-search .wy-dropdown > a img.logo, .wy-side-nav-search > a img.logo { + width: 200px; +} diff --git a/docs/_templates/.gitkeep b/docs/_templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..02b57ba --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,126 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import datetime +import os +import sys + +read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True" + +sys.path.insert(0, os.path.join(os.path.abspath(".."), "src")) + +source_suffix = ".rst" +master_doc = "index" +pygments_style = "sphinx" +html_theme_options = {"logo_only": True} +html_logo = "_static/logo.png" + + +# -- Project information ----------------------------------------------------- + +project = "Anemoi Transform" + +author = "ECMWF" + +year = datetime.datetime.now().year +if year == 2024: + years = "2024" +else: + years = "2024-%s" % (year,) + +copyright = "%s, ECMWF" % (years,) + +try: + from anemoi.transform._version import __version__ + + release = __version__ +except ImportError: + release = "0.0.0" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.todo", + "sphinx_rtd_theme", + "nbsphinx", + "sphinx.ext.graphviz", + "sphinx.ext.intersphinx", + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinxarg.ext", +] + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "'**.ipynb_checkpoints'"] + +intersphinx_mapping = { + "python": ("https://python.readthedocs.io/en/latest", None), + "anemoi-utils": ( + "https://anemoi-utils.readthedocs.io/en/latest/", + ("../../anemoi-utils/docs/_build/html/objects.inv", None), + ), + "anemoi-datasets": ( + "https://anemoi-datasets.readthedocs.io/en/latest/", + ("../../anemoi-datasets/docs/_build/html/objects.inv", None), + ), + "anemoi-models": ( + "https://anemoi-models.readthedocs.io/en/latest/", + ("../../anemoi-models/docs/_build/html/objects.inv", None), + ), + "anemoi-training": ( + "https://anemoi-training.readthedocs.io/en/latest/", + ("../../anemoi-training/docs/_build/html/objects.inv", None), + ), + "anemoi-inference": ( + "https://anemoi-inference.readthedocs.io/en/latest/", + ("../../anemoi-inference/docs/_build/html/objects.inv", None), + ), + "anemoi-graphs": ( + "https://anemoi-graphs.readthedocs.io/en/latest/", + ("../../anemoi-graphs/docs/_build/html/objects.inv", None), + ), + "anemoi-registry": ( + "https://anemoi-registry.readthedocs.io/en/latest/", + ("../../anemoi-registry/docs/_build/html/objects.inv", None), + ), + "anemoi-transform": ( + "https://anemoi-transform.readthedocs.io/en/latest/", + ("../../anemoi-transform/docs/_build/html/objects.inv", None), + ), +} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] +html_css_files = ["style.css"] + + +todo_include_todos = not read_the_docs_build + +autodoc_member_order = "bysource" # Keep file order diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..a32e88c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,62 @@ +.. _anemoi-transform: + +.. _index-page: + +############################################## + Welcome to `anemoi-transform` documentation! +############################################## + +.. warning:: + + This documentation is work in progress. + +*Anemoi* is a framework for developing machine learning weather +forecasting models. It comprises of components or packages for preparing +training datasets, conducting ML model training and a registry for +datasets and trained models. *Anemoi* provides tools for operational +inference, including interfacing to verification software. As a +framework it seeks to handle many of the complexities that +meteorological organisations will share, allowing them to easily train +models from existing recipes but with their own data. + +This package provides a series of utility functions for used by the rest +of the *Anemoi* packages. + +- :doc:`installing` + +.. toctree:: + :maxdepth: 1 + :hidden: + + installing + +********* + Modules +********* + +.. toctree:: + :maxdepth: 1 + :glob: + + modules/* + +***************** + Anemoi packages +***************** + +- :ref:`anemoi-utils ` +- :ref:`anemoi-transform ` +- :ref:`anemoi-datasets ` +- :ref:`anemoi-models ` +- :ref:`anemoi-graphs ` +- :ref:`anemoi-training ` +- :ref:`anemoi-inference ` +- :ref:`anemoi-registry ` + +********* + License +********* + +*Anemoi* is available under the open source `Apache License`__. + +.. __: http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/docs/installing.rst b/docs/installing.rst new file mode 100644 index 0000000..1055752 --- /dev/null +++ b/docs/installing.rst @@ -0,0 +1,31 @@ +############ + Installing +############ + +To install the package, you can use the following command: + +.. code:: bash + + pip install anemoi-transform[...options...] + +The options are: + +- ``dev``: install the development dependencies +- ``all``: install all the dependencies + +************** + Contributing +************** + +.. code:: bash + + git clone git@github.com:ecmwf/anemoi-transform.git + cd anemoi-transform + pip install .[dev] + pip install -r docs/requirements.txt + +You may also have to install pandoc on MacOS: + +.. code:: bash + + brew install pandoc diff --git a/docs/modules/placeholder.rst b/docs/modules/placeholder.rst new file mode 100644 index 0000000..68d1836 --- /dev/null +++ b/docs/modules/placeholder.rst @@ -0,0 +1,8 @@ +############# + placeholder +############# + +.. automodule:: anemoi.transform.placeholder + :members: + :no-undoc-members: + :show-inheritance: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4dcaca3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# (C) Copyright 2024 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + +# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ + +[build-system] +requires = [ "setuptools>=60", "setuptools-scm>=8" ] + +[project] +name = "anemoi-transform" + +description = "A package to hold various data transformation functions to support training of ML models on ECMWF data." +keywords = [ "ai", "tools" ] + +license = { file = "LICENSE" } +authors = [ + { name = "European Centre for Medium-Range Weather Forecasts (ECMWF)", email = "software.support@ecmwf.int" }, +] + +requires-python = ">=3.9" + +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + +dynamic = [ "version" ] +dependencies = [ + "earthkit-data", + "earthkit-meteo", +] + +optional-dependencies.all = [ "anemoi-transform" ] +optional-dependencies.dev = [ "anemoi-transform[all,docs,tests]" ] + +optional-dependencies.docs = [ + "nbsphinx", + "pandoc", + "requests", + "sphinx", + "sphinx-argparse<0.5", + "sphinx-rtd-theme", + "termcolor", +] + +optional-dependencies.tests = [ "pytest" ] + +urls.Documentation = "https://anemoi-transform.readthedocs.io/" +urls.Homepage = "https://github.com/ecmwf/anemoi-transform/" +urls.Issues = "https://github.com/ecmwf/anemoi-transform/issues" +urls.Repository = "https://github.com/ecmwf/anemoi-transform/" + +scripts.anemoi-transform = "anemoi.transform.__main__:main" + +[tool.setuptools_scm] +version_file = "src/anemoi/transform/_version.py" diff --git a/src/anemoi/transform/__init__.py b/src/anemoi/transform/__init__.py new file mode 100644 index 0000000..7a799a1 --- /dev/null +++ b/src/anemoi/transform/__init__.py @@ -0,0 +1,9 @@ +# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts. +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + + +from ._version import __version__ diff --git a/src/anemoi/transform/__main__.py b/src/anemoi/transform/__main__.py new file mode 100644 index 0000000..be940c2 --- /dev/null +++ b/src/anemoi/transform/__main__.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# (C) Copyright 2024 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +from anemoi.utils.cli import cli_main +from anemoi.utils.cli import make_parser + +from . import __version__ +from .commands import COMMANDS + + +# For read-the-docs +def create_parser(): + return make_parser(__doc__, COMMANDS) + + +def main(): + cli_main(__version__, __doc__, COMMANDS) + + +if __name__ == "__main__": + main() diff --git a/src/anemoi/transform/commands/__init__.py b/src/anemoi/transform/commands/__init__.py new file mode 100644 index 0000000..cebb539 --- /dev/null +++ b/src/anemoi/transform/commands/__init__.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# (C) Copyright 2024 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + +import os + +from anemoi.utils.cli import Command +from anemoi.utils.cli import Failed +from anemoi.utils.cli import register_commands + +__all__ = ["Command"] + +COMMANDS = register_commands( + os.path.dirname(__file__), + __name__, + lambda x: x.command(), + lambda name, error: Failed(name, error), +) diff --git a/src/anemoi/transform/commands/hello.py b/src/anemoi/transform/commands/hello.py new file mode 100644 index 0000000..793d126 --- /dev/null +++ b/src/anemoi/transform/commands/hello.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# (C) Copyright 2024 ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. +# + + +from . import Command + + +class Hello(Command): + + def add_arguments(self, command_parser): + command_parser.add_argument("--what", help="Say hello to someone") + + def run(self, args): + if args.what: + print(f"Hello {args.what}!") + else: + print("Hello!") + + +command = Hello diff --git a/src/anemoi/transform/placeholder.py b/src/anemoi/transform/placeholder.py new file mode 100644 index 0000000..1b4d478 --- /dev/null +++ b/src/anemoi/transform/placeholder.py @@ -0,0 +1,9 @@ +# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts. +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + + +# Remove this file when we have a few functions diff --git a/tests/test_transform.py b/tests/test_transform.py new file mode 100644 index 0000000..edacad9 --- /dev/null +++ b/tests/test_transform.py @@ -0,0 +1,17 @@ +# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts. +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + + +def test_transform(): + pass + + +if __name__ == "__main__": + for name, obj in list(globals().items()): + if name.startswith("test_") and callable(obj): + print(f"Running {name}...") + obj()