diff --git a/.github/ci-config.yml b/.github/ci-config.yml new file mode 100644 index 0000000..a1f0911 --- /dev/null +++ b/.github/ci-config.yml @@ -0,0 +1,10 @@ +dependencies: | + ecmwf/ecbuild + MathisRosenhauer/libaec@refs/tags/v1.1.3 + ecmwf/eccodes + ecmwf/eckit + ecmwf/atlas + ecmwf/mir +dependency_branch: develop +parallelism_factor: 8 +self_build: false # Only for python packages diff --git a/.github/ci-hpc-config.yml b/.github/ci-hpc-config.yml index 2e20c20..6507efa 100644 --- a/.github/ci-hpc-config.yml +++ b/.github/ci-hpc-config.yml @@ -1,3 +1,16 @@ build: python: 3.10 - parallel: 1 + dependencies: + - ecmwf/ecbuild@develop + - MathisRosenhauer/libaec@refs/tags/v1.1.3 + - ecmwf/eccodes@develop + - ecmwf/eckit@feature/eckit-geo + - ecmwf/atlas@develop + - ecmwf/mir@feature/eckit-geo + python_dependencies: + - ecmwf/eccodes-python@develop + - ecmwf/earthkit-data@develop + parallel: 64 + pytest_cmd: | + pytest --cov=./ --cov-report=xml + python -m coverage report diff --git a/.github/workflows/cd-pypi.yml b/.github/workflows/cd-pypi.yml index bd03198..44b14e9 100644 --- a/.github/workflows/cd-pypi.yml +++ b/.github/workflows/cd-pypi.yml @@ -2,7 +2,7 @@ name: cd on: push: tags: - - '**' + - '**' jobs: pypi: uses: ecmwf/reusable-workflows/.github/workflows/cd-pypi.yml@v2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad93595..9ed1e9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,26 +3,26 @@ on: # Trigger the workflow on push to master or develop, except tag creation push: branches: - - 'main' - - 'develop' + - 'main' + - 'develop' tags-ignore: - - '**' + - '**' paths-ignore: - - "docs/**" - - "README.md" + - "docs/**" + - "README.md" # Trigger the workflow on pull request pull_request: paths-ignore: - - "docs/**" - - "README.md" + - "docs/**" + - "README.md" # Trigger the workflow manually workflow_dispatch: # Trigger after public PR approved for CI pull_request_target: types: [labeled] paths-ignore: - - "docs/**" - - "README.md" + - "docs/**" + - "README.md" jobs: # Run CI including downstream packages on self-hosted runners downstream-ci: diff --git a/.github/workflows/legacy-ci.yml b/.github/workflows/legacy-ci.yml deleted file mode 100644 index 09b98ad..0000000 --- a/.github/workflows/legacy-ci.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: legacy-ci -on: - push: - branches: - - main - - develop - tags: - - "*" - pull_request: - branches: - - main - - develop - pull_request_target: - types: [labeled] - workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -defaults: - run: - shell: bash -l {0} -jobs: - pre-commit: - if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha || github.ref }} - - uses: actions/setup-python@v4 - with: - python-version: 3.x - - uses: pre-commit/action@v3.0.0 - documentation: - if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }} - runs-on: ubuntu-latest - defaults: - run: - shell: bash -l {0} - steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha || github.ref }} - - name: Install Conda environment with Micromamba - uses: mamba-org/setup-micromamba@v1 - with: - environment-file: tests/environment-unit-tests.yml - environment-name: DEVELOP - channels: conda-forge - cache-env: true - cache-env-key: ubuntu-latest-3.10 - extra-specs: | - python=3.10 - - name: Install package - run: | - python -m pip install --no-deps -e . - - name: Build documentation - run: | - make docs-build diff --git a/.github/workflows/notify-new-issue.yml b/.github/workflows/notify-new-issue.yml index 3593edc..4896baa 100644 --- a/.github/workflows/notify-new-issue.yml +++ b/.github/workflows/notify-new-issue.yml @@ -2,12 +2,12 @@ name: Notify new issue on: issues: types: - - "opened" + - "opened" jobs: notify: runs-on: ubuntu-latest steps: - - name: Notify new issue - uses: ecmwf/notify-teams-issue@v1 - with: - incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} + - name: Notify new issue + uses: ecmwf/notify-teams-issue@v1 + with: + incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} diff --git a/.github/workflows/notify-new-pr.yml b/.github/workflows/notify-new-pr.yml index af5dd82..2c22eba 100644 --- a/.github/workflows/notify-new-pr.yml +++ b/.github/workflows/notify-new-pr.yml @@ -2,12 +2,12 @@ name: Notify new PR on: pull_request_target: types: - - "opened" + - "opened" jobs: notify: runs-on: ubuntu-latest steps: - - name: Notify new PR - uses: ecmwf/notify-teams-pr@v1 - with: - incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} + - name: Notify new PR + uses: ecmwf/notify-teams-pr@v1 + with: + incoming_webhook: ${{ secrets.MS_TEAMS_INCOMING_WEBHOOK }} diff --git a/.github/workflows/python-pull-request.yml b/.github/workflows/python-pull-request.yml new file mode 100644 index 0000000..8f32929 --- /dev/null +++ b/.github/workflows/python-pull-request.yml @@ -0,0 +1,10 @@ +name: Code Quality checks for PRs +on: + push: + pull_request: + types: [opened, synchronize, reopened] +jobs: + quality: + uses: ecmwf/reusable-workflows/.github/workflows/qa-precommit-run.yml@v2 + with: + skip-hooks: "no-commit-to-branch" diff --git a/.gitignore b/.gitignore index 4c3211e..7728be8 100644 --- a/.gitignore +++ b/.gitignore @@ -360,3 +360,11 @@ tags # local code _dev + + +# data and precomputed weights +*.grib +*.grib1 +*.grib2 +*.json +*.npz diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 986d81b..804d502 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,54 +1,41 @@ repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: trailing-whitespace # Trailing whitespace checker - - id: end-of-file-fixer # Ensure files end in a newline - - id: check-json - - id: check-yaml # Check YAML files for syntax errors only - args: [--unsafe, --allow-multiple-documents] - - id: check-toml - # - id: check-added-large-files - - id: debug-statements # Check for debugger imports and py37+ breakpoint() - - id: mixed-line-ending - - id: no-commit-to-branch # Prevent committing to main / master - - id: check-merge-conflict # Check for files that contain merge conflict - exclude: /README\.rst$|^docs/.*\.rst$ - - repo: https://github.com/psf/black - rev: 24.8.0 - hooks: - - id: black - args: [--line-length=120] - - repo: https://github.com/keewis/blackdoc - rev: v0.3.8 - hooks: - - id: blackdoc - additional_dependencies: [black==23.3.0] - exclude: xr_engine_profile_rst\.py - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 - hooks: - - id: ruff - exclude: '(dev/.*|.*_)\.py$' - args: - - --line-length=120 - - --fix - - --exit-non-zero-on-fix - - --preview - - repo: https://github.com/executablebooks/mdformat - rev: 0.7.14 - hooks: - - id: mdformat - exclude: cruft-update-template.md - - repo: https://github.com/google/yamlfmt - rev: v0.13.0 - hooks: - - id: yamlfmt - - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v1.0.0 - hooks: - - id: sphinx-lint - - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.5.0" - hooks: - - id: pyproject-fmt +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace # Trailing whitespace checker + - id: end-of-file-fixer # Ensure files end in a newline + - id: check-json + - id: check-yaml # Check YAML files for syntax errors only + args: [--unsafe, --allow-multiple-documents] + - id: check-toml + # - id: check-added-large-files + - id: debug-statements # Check for debugger imports and py37+ breakpoint() + - id: mixed-line-ending + - id: no-commit-to-branch # Prevent committing to main / master + - id: check-merge-conflict # Check for files that contain merge conflict + exclude: /README\.rst$|^docs/.*\.rst$ +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.4 + hooks: + - id: ruff-check + exclude: '(docs/conf|dev/.*|.*_)\.py$|docs/experimental/.*\.ipynb$' + args: + - --fix + - --exit-non-zero-on-fix + - id: ruff-format +- repo: https://github.com/executablebooks/mdformat + rev: 0.7.14 + hooks: + - id: mdformat + exclude: cruft-update-template.md +- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.16.0 + hooks: + - id: pretty-format-yaml + args: [--autofix, --preserve-quotes] + - id: pretty-format-toml + args: [--autofix] +- repo: https://github.com/sphinx-contrib/sphinx-lint + rev: v1.0.0 + hooks: + - id: sphinx-lint diff --git a/.readthedocs.yml b/.readthedocs.yml index 5a61cc6..900dcb4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,9 +3,11 @@ formats: [] build: os: "ubuntu-22.04" tools: - python: "3.9" + python: "3.10" python: install: - - requirements: docs/requirements.txt + - requirements: docs/requirements.txt + - method: pip + path: . sphinx: configuration: docs/conf.py diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..6ad9797 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,145 @@ +@import url("https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap"); + +:root { + --font-stack: "Nunito", sans-serif; + --font-stack--monospace: "Nunito", sans-serif; +} + +body { + font-family: "Nunito", sans-serif; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "Nunito", sans-serif; +} + +/* Sidebar solid background colour */ +.sidebar-drawer { + background: #262640; +} + +/* SVG overlay – absolute on mobile so it collapses with the drawer */ +.sidebar-drawer::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url("octahedral-grid.svg") no-repeat top center / auto 150vh; + opacity: 0.08; + pointer-events: none; + z-index: 0; +} + +/* + On desktop, switch the overlay to position: fixed so it stays in place + while scrolling (avoids background-attachment:fixed Safari flicker). + Width values mirror Furo's own sidebar-drawer widths at each breakpoint. +*/ +@media screen and (min-width: 63em) { + .sidebar-drawer::before { + position: fixed; + top: 0; + left: 0; + width: calc(50% - 18.5em); + min-width: 15em; + height: 100vh; + } +} + +@media screen and (min-width: 82em) { + .sidebar-drawer::before { + width: calc(50% - 26em); + } +} + +/* ── xarray dark-mode fix for Furo "auto" theme ────────────────────── + xarray's inline
<xarray.Dataset> Size: 44kB\n", + "Dimensions: (step: 2, latitude: 37, longitude: 72)\n", + "Coordinates:\n", + " * step (step) timedelta64[ns] 16B 00:00:00 12:00:00\n", + " * latitude (latitude) float64 296B 90.0 85.0 80.0 75.0 ... -80.0 -85.0 -90.0\n", + " * longitude (longitude) float64 576B 0.0 5.0 10.0 15.0 ... 345.0 350.0 355.0\n", + "Data variables:\n", + " 2t (step, latitude, longitude) float64 43kB ...\n", + "Attributes:\n", + " Conventions: CF-1.8\n", + " institution: ECMWF" + ], + "text/plain": [ + "
<xarray.Dataset> Size: 44kB\n", + "Dimensions: (step: 2, latitude: 37, longitude: 72)\n", + "Coordinates:\n", + " * step (step) timedelta64[ns] 16B 00:00:00 12:00:00\n", + " * latitude (latitude) float64 296B 90.0 85.0 80.0 75.0 ... -80.0 -85.0 -90.0\n", + " * longitude (longitude) float64 576B 0.0 5.0 10.0 15.0 ... 345.0 350.0 355.0\n", + "Data variables:\n", + " 2t (step, latitude, longitude) float64 43kB ...\n", + "Attributes:\n", + " Conventions: CF-1.8\n", + " institution: ECMWF
| Name | Value | Default |
|---|---|---|
| cache-policy | 'user' | 'user' |
| check-out-of-date-urls | False | False |
| download-out-of-date-urls | False | False |
| maximum-cache-disk-usage | None | None |
| maximum-cache-size | '5GB' | '5GB' |
| maximum-weights-memory-cache-size | '500MB' | '500MB' |
| temporary-cache-directory-root | None | None |
| temporary-directory-root | None | None |
| url-download-timeout | '30s' | '30s' |
| user-cache-directory | '/Users/cgr/.cache/earthkit-geo' | '/Users/cgr/.cache/earthkit-geo' |
| version | '0.5.2.dev118+g382d2cd13.d20260302' | '' |
| weights-memory-cache-policy | 'largest' | 'largest' |
| weights-memory-cache-strict-mode | False | False |
| Name | Value | Default |
|---|---|---|
| cache-policy | 'user' | 'user' |
| check-out-of-date-urls | False | False |
| download-out-of-date-urls | False | False |
| maximum-cache-disk-usage | None | None |
| maximum-cache-size | '5GB' | '5GB' |
| temporary-cache-directory-root | None | None |
| temporary-directory-root | None | None |
| url-download-timeout | EARTHKIT_GEO_URL_DOWNLOAD_TIMEOUT='26' (10) | '30s' |
| user-cache-directory | '/Users/cgr/.cache/earthkit-geo' | '/Users/cgr/.cache/earthkit-geo' |
| version | '0.5.2.dev114+g40cc1015b.d20260219' | '' |
<xarray.Dataset> Size: 44kB\n", + "Dimensions: (step: 2, latitude: 37, longitude: 72)\n", + "Coordinates:\n", + " * step (step) timedelta64[ns] 16B 00:00:00 12:00:00\n", + " * latitude (latitude) float64 296B 90.0 85.0 80.0 75.0 ... -80.0 -85.0 -90.0\n", + " * longitude (longitude) float64 576B 0.0 5.0 10.0 15.0 ... 345.0 350.0 355.0\n", + "Data variables:\n", + " 2t (step, latitude, longitude) float64 43kB ...\n", + "Attributes:\n", + " Conventions: CF-1.8\n", + " institution: ECMWF
<xarray.Dataset> Size: 44kB\n", + "Dimensions: (step: 2, latitude: 37, longitude: 72)\n", + "Coordinates:\n", + " * step (step) timedelta64[ns] 16B 00:00:00 12:00:00\n", + " * latitude (latitude) float64 296B 90.0 85.0 80.0 75.0 ... -80.0 -85.0 -90.0\n", + " * longitude (longitude) float64 576B 0.0 5.0 10.0 15.0 ... 345.0 350.0 355.0\n", + "Data variables:\n", + " 2t (step, latitude, longitude) float64 43kB ...\n", + "Attributes:\n", + " Conventions: CF-1.8\n", + " institution: ECMWF
| %s | " % (n["path"],)) + + for k in [x for x in n.keys() if x not in ("path", "owner_data")]: + v = humanize.bytes(n[k]) if k == "size" else n[k] + html.append("%s | %s | " % (k, v)) + html.append("
| %s | ".join(columns))) + + for k, v in sorted(self._config.items()): + config = CONFIG_AND_HELP.get(k, None) + default = config.default if config else "" + if k in env: + html.append( + " | |
|---|---|---|
| %s | %s=%r (%r) | %r |
| %s | %r | %r |