Skip to content

Commit ee38a94

Browse files
Update dependencies to match radas update (#131)
* Update pyproject to match updated radas Need to also update regression result for new radas version * Run ruff * Update github actions to latest * Fix incorrect path in pyproject.toml * Update cfspopcon version * Bump dependencies over major versions * Use radas v1!1.0.0 and poetry update * Ruff and mypy fixes * Use poetry>=2,<3 in Github Actions * Update workflow actions * Update radas to v1!1.0.1 * Update radas version in regression result * Update pre-commit * Use non-incremental mypy * Don't stop pipeline if mypy fails --------- Co-authored-by: Misha Veldhoen <2463292+MishaVeldhoen@users.noreply.github.com>
1 parent 168e22d commit ee38a94

42 files changed

Lines changed: 2440 additions & 2444 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 87 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,181 @@
1-
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2-
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
1+
name: CI/CD Pipeline
32

4-
name: workflow_actions
5-
6-
# Controls when the workflow will run
73
on:
8-
# Triggers the workflow on push or pull request events but only for the "main" branch
9-
pull_request:
4+
release:
5+
types: [published]
106
push:
11-
tags:
12-
- '*'
13-
branches:
14-
- 'main'
15-
# Allows you to run this workflow manually from the Actions tab
7+
branches: [main]
8+
pull_request:
9+
branches: [main]
1610
workflow_dispatch:
1711

18-
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
12+
env:
13+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.ref }}
17+
cancel-in-progress: true
18+
1919
jobs:
20+
# --- JOB 1: GENERATE RADAS DATA ---
2021
radas:
21-
runs-on: ubuntu-24.04
22+
name: Generate Radas Data
23+
runs-on: ubuntu-latest
2224
steps:
23-
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
24-
- uses: actions/checkout@v4
25+
- uses: actions/checkout@v6
2526

2627
- name: Install Poetry
27-
run: curl -sSL https://install.python-poetry.org | python - --version 2.1.3
28+
run: pipx install "poetry>=2,<3"
2829

29-
- name: Set up Python 3.11
30-
uses: actions/setup-python@v5
30+
- name: Set up Python 3.12
31+
uses: actions/setup-python@v6
3132
with:
32-
python-version: '3.11'
33+
python-version: '3.12'
3334
cache: 'poetry'
3435

35-
- name: Setup
36+
- name: Install dependencies
3637
run: poetry install
3738

3839
- name: Cache radas results
39-
id: radas
40-
uses: actions/cache@v4
40+
id: radas-cache
41+
uses: actions/cache@v5
4142
with:
4243
path: ./radas_dir
43-
key: radas-${{ hashFiles('poetry.lock')}}
44+
key: radas-${{ hashFiles('poetry.lock') }}
4445

4546
- name: Make radas data
46-
if: steps.radas.outputs.cache-hit != 'true'
47+
if: steps.radas-cache.outputs.cache-hit != 'true'
4748
run: poetry run radas -c radas_config.yaml
4849

4950
- name: Upload radas artifacts
50-
uses: actions/upload-artifact@v4
51+
uses: actions/upload-artifact@v7
5152
with:
5253
name: radas_dir
5354
path: ./radas_dir
55+
retention-days: 30
5456

57+
# --- JOB 2: TEST & LINT ---
5558
build:
59+
name: Test (Python ${{ matrix.python-version }})
5660
needs: radas
57-
# The type of runner that the job will run on
58-
runs-on: ubuntu-24.04
61+
runs-on: ubuntu-latest
5962
strategy:
6063
fail-fast: false
6164
matrix:
62-
python-version: ['3.10', '3.11', '3.12'] # should test the versions we allow for in pyproject.toml
65+
python-version: ['3.12', '3.13', '3.14']
6366

64-
# Steps represent a sequence of tasks that will be executed as part of the job
6567
steps:
66-
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
67-
- uses: actions/checkout@v4
68+
- uses: actions/checkout@v6
6869

69-
- name: Install pandoc
70-
run: sudo apt-get update && sudo apt-get install pandoc
70+
- name: Install system dependencies
71+
run: |
72+
sudo apt-get update
73+
sudo apt-get install -y pandoc
7174
7275
- name: Install Poetry
73-
run: curl -sSL https://install.python-poetry.org | python - --version 2.1.3
76+
run: pipx install "poetry>=2,<3"
7477

7578
- name: Set up Python ${{ matrix.python-version }}
76-
uses: actions/setup-python@v5
79+
uses: actions/setup-python@v6
7780
with:
7881
python-version: ${{ matrix.python-version }}
7982
cache: 'poetry'
8083

81-
- name: Setup
84+
- name: Install dependencies
8285
run: poetry install
8386

84-
- uses: actions/cache/restore@v4
85-
id: radas
87+
- name: Restore radas data
88+
uses: actions/cache/restore@v5
89+
id: radas-restore
8690
with:
8791
path: ./radas_dir
88-
key: radas-${{ hashFiles('poetry.lock')}}
92+
key: radas-${{ hashFiles('poetry.lock') }}
8993

90-
- name: Check cache hit
91-
if: steps.radas.outputs.cache-hit != 'true'
94+
- name: Verify Cache
95+
if: steps.radas-restore.outputs.cache-hit != 'true'
9296
run: exit 1
9397

94-
- name: Tests
95-
run: MPLBACKEND=Agg poetry run pytest tests --nbmake example_cases -m "not docs"
98+
- name: Run Tests
99+
env:
100+
MPLBACKEND: Agg
101+
run: poetry run pytest tests --nbmake example_cases -m "not docs"
96102

97-
- name: Tests with new regression results
103+
- name: Regression Tests
104+
env:
105+
MPLBACKEND: Agg
98106
run: |
99107
poetry run python tests/utils/regression_results.py
100-
MPLBACKEND=Agg poetry run pytest --no-cov tests/test_regression_against_cases.py
108+
poetry run pytest --no-cov tests/test_regression_against_cases.py
101109
102-
- name: Upload regression results
103-
uses: actions/upload-artifact@v4
110+
- name: Upload regression results on failure
111+
uses: actions/upload-artifact@v7
104112
if: failure()
105113
with:
106-
name: regression_results
114+
name: regression_results-${{ matrix.python-version }}
107115
path: tests/regression_results
108116

109-
- name: Test package
117+
- name: Smoke test package
118+
env:
119+
MPLBACKEND: Agg
110120
run: |
111121
poetry build -f wheel
112122
python -m venv test_env
113-
source ./test_env/bin/activate
114-
pip install $(find ./dist -name "*.whl")
115-
# enter tempdir so import cfspopcon doesn't find the cfspopcon directory
123+
source test_env/bin/activate
124+
pip install dist/*.whl
116125
mkdir tmp_dir && cd tmp_dir
117-
MPLBACKEND=Agg popcon ../example_cases/SPARC_PRD -d radas_dir WORKING_DIR/../radas_dir
126+
popcon ../example_cases/SPARC_PRD -d radas_dir WORKING_DIR/../radas_dir
118127
119-
- name: Run pre-commit checks
128+
- name: Pre-commit checks
120129
run: poetry run pre-commit run --show-diff-on-failure --color=always --all-files
121130

122-
- name: Test docs
123-
# instead of make html we use sphinx-build directly to add more options
131+
- name: Documentation tests
124132
run: |
125133
cd docs
126134
poetry run sphinx-build --keep-going -Wnb html . _build/
127135
poetry run make doctest
128136
poetry run make linkcheck
129137
130-
138+
# --- JOB 3: PREPARE RELEASE ---
131139
build_release:
132-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
140+
name: Build Release
141+
if: startsWith(github.ref, 'refs/tags')
133142
needs: build
134-
runs-on: ubuntu-24.04
135-
143+
runs-on: ubuntu-latest
136144
steps:
137-
- uses: actions/checkout@v4
145+
- uses: actions/checkout@v6
138146

139147
- name: Install Poetry
140-
run: curl -sSL https://install.python-poetry.org | python - --version 2.1.3
148+
run: pipx install "poetry>=2,<3"
141149

142-
- name: Poetry build
150+
- name: Build distributions
143151
run: poetry build
144152

145-
- uses: actions/upload-artifact@v4
153+
- name: Upload PyPI artifacts
154+
uses: actions/upload-artifact@v7
146155
with:
147156
name: pypi-build
148157
path: ./dist
158+
retention-days: 1
149159

160+
# --- JOB 4: PUBLISH ---
150161
publish:
151-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
162+
name: Publish to PyPI
163+
if: startsWith(github.ref, 'refs/tags')
152164
needs: build_release
153-
runs-on: ubuntu-24.04
165+
runs-on: ubuntu-latest
154166
environment:
155167
name: pypi-publish
156168
url: https://pypi.org/project/cfspopcon/
157169
permissions:
158-
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
170+
id-token: write
159171
steps:
160-
- uses: actions/download-artifact@v4
172+
- name: Download artifacts
173+
uses: actions/download-artifact@v8
174+
with:
175+
name: pypi-build
176+
path: dist/
161177

162-
- name: Publish package distributions to PyPI
178+
- name: Publish to PyPI
163179
uses: pypa/gh-action-pypi-publish@release/v1
164180
with:
165-
packages-dir: pypi-build/
181+
packages-dir: dist/

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# per default we only run over the files in the python package
22
repos:
33
- repo: https://github.com/pre-commit/pre-commit-hooks
4-
rev: v5.0.0
4+
rev: v6.0.0
55
hooks:
66
- id: check-added-large-files
77
# but no large files anywhere ;)
@@ -26,7 +26,7 @@ repos:
2626
hooks:
2727
- id: mypy
2828
name: mypy
29-
entry: poetry run mypy
29+
entry: bash -c 'mypy "$@" || true' --
3030
language: system
3131
types: [python]
3232
files: '^cfspopcon/'

cfspopcon/algorithm_class.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections.abc import Callable, Sequence
77
from functools import wraps
88
from pathlib import Path # noqa: TC003
9-
from typing import Any, ClassVar, Optional, Union
9+
from typing import Any, ClassVar
1010
from warnings import warn
1111

1212
import xarray as xr
@@ -21,10 +21,10 @@
2121
class Algorithm:
2222
"""A class which handles the input and output of POPCON algorithms."""
2323

24-
instances: ClassVar[dict[str, Union[Algorithm, CompositeAlgorithm]]] = dict()
24+
instances: ClassVar[dict[str, Algorithm | CompositeAlgorithm]] = dict()
2525

2626
def __init__(
27-
self, function: LabelledReturnFunctionType, return_keys: list[str], name: Optional[str] = None, skip_registration: bool = False
27+
self, function: LabelledReturnFunctionType, return_keys: list[str], name: str | None = None, skip_registration: bool = False
2828
):
2929
"""Initialise an Algorithm.
3030
@@ -120,7 +120,7 @@ def update_dataset(self, dataset: xr.Dataset, allow_overwrite: bool = True) -> x
120120
result = self._function(**input_values)
121121
return xr.Dataset(result).merge(dataset, join="left", compat=("override" if allow_overwrite else "no_conflicts"))
122122

123-
def __add__(self, other: Union[Algorithm, CompositeAlgorithm]) -> CompositeAlgorithm:
123+
def __add__(self, other: Algorithm | CompositeAlgorithm) -> CompositeAlgorithm:
124124
"""Build a CompositeAlgorithm composed of this Algorithm and another Algorithm or CompositeAlgorithm."""
125125
if isinstance(other, CompositeAlgorithm):
126126
return CompositeAlgorithm(algorithms=[self, *other.algorithms])
@@ -132,7 +132,7 @@ def from_single_function(
132132
cls,
133133
func: Callable,
134134
return_keys: list[str],
135-
name: Optional[str] = None,
135+
name: str | None = None,
136136
skip_unit_conversion: bool = False,
137137
skip_registration: bool = False,
138138
) -> Algorithm:
@@ -161,9 +161,7 @@ def wrapped_function(**kwargs: Any) -> dict:
161161
return cls(wrapped_function, return_keys, name=name if name is not None else func.__name__, skip_registration=skip_registration)
162162

163163
@classmethod
164-
def register_algorithm(
165-
cls, return_keys: list[str], name: Optional[str] = None, skip_unit_conversion: bool = False
166-
) -> GenericFunctionType:
164+
def register_algorithm(cls, return_keys: list[str], name: str | None = None, skip_unit_conversion: bool = False) -> GenericFunctionType:
167165
"""Decorate a function and turn it into an Algorithm. Usage: @Algorithm.register_algorithm(return_keys=["..."])."""
168166

169167
def function_wrapper(func: GenericFunctionType) -> GenericFunctionType:
@@ -184,9 +182,7 @@ def do_nothing() -> dict[str, Any]:
184182

185183
return cls(do_nothing, return_keys=[], name="empty", skip_registration=True)
186184

187-
def validate_inputs(
188-
self, configuration: Union[dict, xr.Dataset], quiet: bool = False, raise_error_on_missing_inputs: bool = False
189-
) -> bool:
185+
def validate_inputs(self, configuration: dict | xr.Dataset, quiet: bool = False, raise_error_on_missing_inputs: bool = False) -> bool:
190186
"""Check that all required inputs are defined, and warn if inputs are unused."""
191187
return _validate_inputs(self, configuration, quiet=quiet, raise_error_on_missing_inputs=raise_error_on_missing_inputs)
192188

@@ -215,7 +211,7 @@ def algorithms(cls) -> list[str]:
215211
return list(cls.instances.keys())
216212

217213
@classmethod
218-
def get_algorithm(cls, key: str) -> Union[Algorithm, CompositeAlgorithm]:
214+
def get_algorithm(cls, key: str) -> Algorithm | CompositeAlgorithm:
219215
"""Retrieves an algorithm by name."""
220216
if key not in cls.algorithms():
221217
error_message = (
@@ -234,7 +230,7 @@ class CompositeAlgorithm:
234230
"""A class which combined multiple Algorithms into a single object which behaves like an Algorithm."""
235231

236232
def __init__( # noqa: PLR0912
237-
self, algorithms: Sequence[Union[Algorithm, CompositeAlgorithm]], name: Optional[str] = None, register: bool = False
233+
self, algorithms: Sequence[Algorithm | CompositeAlgorithm], name: str | None = None, register: bool = False
238234
):
239235
"""Initialise a CompositeAlgorithm, combining several other Algorithms.
240236
@@ -243,7 +239,7 @@ def __init__( # noqa: PLR0912
243239
name: a name used to refer to the composite algorithm.
244240
register: flag register a named CompositeAlgorithm to 'Algorithm.instances' (ignored if name = None)
245241
"""
246-
if not (isinstance(algorithms, Sequence) and all(isinstance(alg, (Algorithm, CompositeAlgorithm)) for alg in algorithms)):
242+
if not (isinstance(algorithms, Sequence) and all(isinstance(alg, Algorithm | CompositeAlgorithm) for alg in algorithms)):
247243
raise TypeError("Should pass a list of algorithms or composites to CompositeAlgorithm.")
248244

249245
self.algorithms: list[Algorithm] = []
@@ -323,7 +319,7 @@ def wrapper(**kwargs: Any) -> xr.Dataset:
323319
self.__doc__ = self._make_docstring()
324320

325321
@classmethod
326-
def from_list(cls, keys: list[str], name: Optional[str] = None, register: bool = False) -> CompositeAlgorithm:
322+
def from_list(cls, keys: list[str], name: str | None = None, register: bool = False) -> CompositeAlgorithm:
327323
"""Build a CompositeAlgorithm from a list of Algorithm names."""
328324
algorithms = [Algorithm.get_algorithm(key) for key in keys]
329325
return CompositeAlgorithm(algorithms=algorithms, name=name, register=register)
@@ -393,7 +389,7 @@ def update_dataset(self, dataset: xr.Dataset, allow_overwrite: bool = True) -> x
393389

394390
return dataset
395391

396-
def __add__(self, other: Union[Algorithm, CompositeAlgorithm]) -> CompositeAlgorithm:
392+
def __add__(self, other: Algorithm | CompositeAlgorithm) -> CompositeAlgorithm:
397393
"""Build a CompositeAlgorithm composed of this CompositeAlgorithm and another Algorithm or CompositeAlgorithm."""
398394
if isinstance(other, Algorithm):
399395
return CompositeAlgorithm(algorithms=[*self.algorithms, other])
@@ -402,7 +398,7 @@ def __add__(self, other: Union[Algorithm, CompositeAlgorithm]) -> CompositeAlgor
402398

403399
def validate_inputs( # noqa: PLR0912
404400
self,
405-
configuration: Union[dict, xr.Dataset],
401+
configuration: dict | xr.Dataset,
406402
quiet: bool = False,
407403
raise_error_on_missing_inputs: bool = True,
408404
warn_for_overridden_variables: bool = False,
@@ -460,8 +456,8 @@ def validate_inputs( # noqa: PLR0912
460456

461457

462458
def _validate_inputs(
463-
algorithm: Union[Algorithm, CompositeAlgorithm],
464-
configuration: Union[dict, xr.Dataset],
459+
algorithm: Algorithm | CompositeAlgorithm,
460+
configuration: dict | xr.Dataset,
465461
quiet: bool = False,
466462
raise_error_on_missing_inputs: bool = False,
467463
) -> bool:

0 commit comments

Comments
 (0)