diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6da702de..55510bf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,22 +9,22 @@ jobs: # lint is handled by pre-commit ci conf generate-api: runs-on: ubuntu-latest + env: + FLIT_ROOT_INSTALL: 1 steps: - uses: actions/checkout@v3 - name: Set up Python 3.9 uses: actions/setup-python@v2 with: python-version: 3.9 - - name: Install requirements - run: pip install -r requirements.txt - - name: Install icclim - run: python -m pip install -e . - - name: install black - run: pip install black + - name: Install build tools + run: pip install --upgrade pip flit + - name: Install icclim and dev requirements (dev, test and doc) + run: flit install --deps develop - name: call fun extractor - run: python ./tools/extract-icclim-funs.py icclim/_generated_api.py - - name: run black on generated module - run: black icclim/_generated_api.py + run: python ./tools/extract_icclim_funs.py src/icclim/_generated_api.py + - name: run ruff format on generated module + run: ruff format src/icclim/_generated_api.py - name: create local ref run: | git fetch origin ${{ github.head_ref }} @@ -37,13 +37,15 @@ jobs: - name: commit run: | # Stage the file, commit and push - git add icclim/_generated_api.py + git add src/icclim/_generated_api.py # Ignore error cases git commit -m "MAINT: Update generated API" || true git push origin ${{ github.head_ref }} test: runs-on: ubuntu-latest + env: + FLIT_ROOT_INSTALL: 1 steps: # actions/checkout@v2 checkouts to a new merge commit - uses: actions/checkout@v3 @@ -51,15 +53,12 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.9 - - name: Install requirements - run: pip install -r requirements.txt - - name: Install pytest-cov - run: pip install pytest-cov - - name: Install icclim - run: python -m pip install -e . + - name: Install build tools + run: pip install --upgrade pip flit + - name: Install icclim and dev requirements (dev, test and doc) + run: flit install --deps develop - name: Build coverage file - run: | - pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=icclim icclim/tests | tee pytest-coverage.txt; ( exit ${PIPESTATUS[0]} ) + run: pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=icclim tests/ | tee pytest-coverage.txt; ( exit ${PIPESTATUS[0]} ) - name: Pytest coverage comment id: coverageComment uses: MishaKav/pytest-coverage-comment@main @@ -96,6 +95,8 @@ jobs: logo-update: runs-on: ubuntu-latest + env: + FLIT_ROOT_INSTALL: 1 steps: - uses: actions/checkout@v3 with: @@ -104,12 +105,10 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.9 - - name: Install requirements - run: pip install -r requirements.txt - - name: Install pytest-cov - run: pip install pytest-cov - - name: Install icclim - run: python -m pip install -e . + - name: Install build tools + run: pip install --upgrade pip flit + - name: Install icclim and dev requirements (dev, test and doc) + run: flit install --deps develop - name: Generate logos run: | python ./tools/update_logo_version.py ./doc/source/_static/logo_icclim_colored__base.svg ./doc/source/_static/logo_icclim_colored__displayed.svg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b62b8386..52a3fc45 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,11 +7,6 @@ repos: hooks: - id: absolufy-imports name: absolufy-imports -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 - hooks: - - id: pyupgrade - language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: @@ -20,61 +15,23 @@ repos: - id: end-of-file-fixer language_version: python3 - id: check-yaml + - id: check-toml language_version: python3 - id: debug-statements language_version: python3 - - id: requirements-txt-fixer - language_version: python3 -- repo: https://github.com/psf/black - rev: 23.11.0 - hooks: - - id: black - language_version: python3 - args: ["--target-version", "py37"] -- repo: https://github.com/pycqa/flake8 - rev: 6.1.0 +- repo: https://github.com/pappasam/toml-sort + rev: v0.23.1 hooks: - - id: flake8 - language_version: python3 - additional_dependencies: ['flake8-rst-docstrings'] - args: ['--config=setup.cfg'] -- repo: https://github.com/PyCQA/isort - rev: 5.13.0 - hooks: - - id: isort - language_version: python3 - args: ['--profile', 'black'] -- repo: https://github.com/pycqa/pydocstyle - rev: 6.3.0 + - id: toml-sort-fix +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.8 hooks: - - id: pydocstyle - language_version: python3 - args: ['--convention=numpy', '--match="(?!test_).*\.py"'] + - id: ruff + - id: ruff-format - repo: meta hooks: - id: check-hooks-apply - id: check-useless-excludes -- repo: https://github.com/keewis/blackdoc - rev: v0.3.9 - hooks: - - id: blackdoc - additional_dependencies: [ 'black==22.3.0' ] -#- repo: https://github.com/pre-commit/mirrors-mypy -# rev: v0.910 -# hooks: -# - id: mypy -# additional_dependencies: [ -# # Type stubs -# types-python-dateutil, -# types-pkg_resources, -# types-PyYAML, -# types-pytz, -# # Dependencies that are typed -# numpy, -# xarray, -# xclim, -# typing-extensions==3.10.0.0, -# ] ci: autofix_commit_msg: | diff --git a/.readthedocs.yml b/.readthedocs.yml index e83ea0e6..9a1ac022 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,12 +2,16 @@ version: 2 sphinx: configuration: doc/source/conf.py + fail_on_warning: true build: - os: "ubuntu-20.04" + os: "ubuntu-22.04" tools: python: "3.9" python: - install: - - requirements: requirements_dev.txt + install: + - method: pip + path: . + extra_requirements: + - "doc" diff --git a/README.rst b/README.rst index fb9c03fe..bf247bed 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ |logo| ====== -|build| |pypi| |black| |docs| |conda| |coverage| |doi| +|build| |pypi| |ruff| |docs| |conda| |coverage| |doi| icclim is a Python library to compute climate indices. icclim name stands for index, calculation, climate. @@ -15,7 +15,7 @@ From conda-forge: ``conda install -c conda-forge icclim``. From sources: - Clone the repository ``git clone https://github.com/cerfacs-globc/icclim.git`` - - Install icclim ``python -m setup install`` + - Install icclim ``pip install .`` How to use icclim ----------------- @@ -68,7 +68,7 @@ For a detailed description of each ECA&D index, please visit: https://www.ecad.e .. Pytest Coverage Comment:Begin -.. |coverage| image:: https://img.shields.io/badge/Coverage-91%25-brightgreen.svg +.. |coverage| image:: https://img.shields.io/badge/Coverage-86%25-green.svg :target: https://github.com/cerfacs-globc/icclim/blob/master/README.rst#code-coverage :alt: Code coverage @@ -80,10 +80,6 @@ For a detailed description of each ECA&D index, please visit: https://www.ecad.e :target: https://icclim.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status -.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/python/black - :alt: Python Black - .. |pypi| image:: https://img.shields.io/pypi/v/icclim.svg :target: https://pypi.python.org/pypi/icclim :alt: Python Package Index Build @@ -104,3 +100,7 @@ For a detailed description of each ECA&D index, please visit: https://www.ecad.e :target: https://github.com/cerfacs-globc/icclim :alt: icclim :width: 200px + +.. |ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff diff --git a/doc/source/conf.py b/doc/source/conf.py index 382dd6ce..f75fe5fe 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,3 +1,4 @@ +# noqa: INP001 # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full @@ -10,15 +11,15 @@ # 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 os import sys +from pathlib import Path -sys.path.insert(0, os.path.abspath("../..")) -import icclim +sys.path.insert(0, Path("../..").resolve()) +import icclim # noqa: E402 (import need to be after path update) # -- Project information ----------------------------------------------------- project = "icclim" -copyright = "2021, CERFACS" +copyright = "2021, CERFACS" # noqa: A001 author = "Christian P." version = icclim.__version__ release = icclim.__version__ diff --git a/doc/source/dev/release_process.rst b/doc/source/dev/release_process.rst index 1a6e1992..78981c4e 100644 --- a/doc/source/dev/release_process.rst +++ b/doc/source/dev/release_process.rst @@ -1,49 +1,24 @@ Release process =============== + #. Make sure all tests pass. #. Create and checkout a release branch. -#. Update version number of ICCLIM_VERSION in ``icclim/models/constants.py`` and in ``setup.py``. +#. Update version number of icclim in ``src/icclim/__init__.py``. #. Update release notes in ``doc/source/references/release_notes.rst``. #. Merge release branch to master with a PR. #. Clean dist directory content. -#. Create wheel file on master. - - .. code-block:: sh - - python3 -m setup bdist_wheel - -#. Create source archive. - - .. code-block:: sh - - python3 -m setup sdist - -#. Try to upload on testpypi first. ``twine`` must be installed in your env beforehand. - - .. code-block:: sh - - python3 -m twine upload --repository testpypi dist/* - -#. Try to install testpypi version on a clean virtual environment. +#. Create wheel file on master and source archive. .. code-block:: sh - python3 -m pip install --index-url https://test.pypi.org/simple/ icclim - - .. note:: - - It may fail due to missing dependencies in test.pypi. - In that case, create the environment from icclim environment.yml file to - pull all needed dependencies from conda. - -#. Test basic index such as `icclim.su("simple-tasmax-data.nc")` + python3 -m build -#. Upload to pypi for real. +#. Upload to pypi. .. code-block:: sh - python3 -m twine upload dist/* + flit publish #. Update conda-forge feedstock at https://github.com/conda-forge/icclim-feedstock diff --git a/doc/source/references/ecad_functions_api.rst b/doc/source/references/ecad_functions_api.rst index 3b2cce75..9841dafa 100644 --- a/doc/source/references/ecad_functions_api.rst +++ b/doc/source/references/ecad_functions_api.rst @@ -16,11 +16,12 @@ For example to use this new API with the index `su` you can do: summer_days = icclim.su(in_files=glob.glob("netcdf_files/tasmax*.nc")) -Generated API -------------- +ECAD Generated Functions +------------------------ .. automodule:: icclim._generated_api :members: + :no-index: .. rubric:: Functions diff --git a/doc/source/references/generic_functions_api.rst b/doc/source/references/generic_functions_api.rst index 59f0629b..50d1b9dd 100644 --- a/doc/source/references/generic_functions_api.rst +++ b/doc/source/references/generic_functions_api.rst @@ -4,7 +4,7 @@ Generic indices/indicators ========================== icclim 6.0 introduced the concept of generic indices. -This document present the auto-generated functions that were built base on :ref:`GenericIndicatorRegistry`. +This document present the auto-generated functions that were built base on :py:class:`icclim.generic_indices.GenericIndicatorRegistry`. The are accessible directly from `icclim` namespace. As an example, you can compute the number of days where a threshold is reached with: diff --git a/doc/source/references/release_notes.rst b/doc/source/references/release_notes.rst index 214a262c..06b1c9e2 100644 --- a/doc/source/references/release_notes.rst +++ b/doc/source/references/release_notes.rst @@ -1,6 +1,21 @@ Release history =============== +6.6.0 (unreleased) +------------------ + +* [maint] Migrate from [black, blackdoc, flake8, isort, pyupgrade, pydocstyle] to ruff +* [maint] Migrate from setup.py to pyproject.toml +* [maint] Make readthedocs build fail when there are warnings +* [maint] Fix warnings in doc build +* [maint] Update architecture to have a `src/` and a `tests/` directory at root level +* [maint] Migrate build toolchain from setuptools to flit +* [maint] Remove version number from `constants` module as it was causing the build process to import icclim. + The version number is now statically set in src/icclim/__init__.py +* [maint] Lint code using the more restrictive rules from ruff +* [fix] Force xarray to read dataset sequentially to avoid a netcdf-c threading issue causing seg faults. + + 6.5.0 ----- @@ -259,7 +274,7 @@ Release candidates for 5.0 change logs * [maint] Refactored ecad_functions (removed duplicated code, simplified function signatures...) * [maint] Refactored IndexConfig to hide some technical knowledge which was leaked to other modules. * [enh] Made a basic integration of clix-meta yaml to populate the generated docstring for c3s. -* [maint] This makes pyyaml an required dependency of icclim. +* [maint] This makes pyyaml a required dependency of icclim. * [fix] Fixed an issue with aliasing of "icclim" module and "icclim" package * [maint] Added some metadata to qualify the ecad_indices and recognize the arguments necessary to compute them. * [maint] Added readthedocs CI configuration. This is necessary to use python 3.8. diff --git a/environment.yml b/environment.yml index e2bb5b72..bf11700a 100644 --- a/environment.yml +++ b/environment.yml @@ -13,7 +13,6 @@ dependencies: - dask[array] - netCDF4>=1.5.7 - cftime>=1.5.0 - - pyyaml>=6.0 - rechunker>=0.3.3 - psutil - zarr @@ -24,16 +23,12 @@ dependencies: - pre-commit - pydata-sphinx-theme - sphinx - - black - - flake8 - - isort + - ruff - pip - - pylint - pytest - pytest-cov - setuptools - - twine - - flake8-rst-docstrings + - flit # Extra dependencies - matplotlib - cartopy diff --git a/icclim/icclim_types.py b/icclim/icclim_types.py deleted file mode 100644 index 1434bbd5..00000000 --- a/icclim/icclim_types.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -from typing import Dict, List, Literal, Sequence, Tuple, Union - -from xarray import DataArray, Dataset - -InFileBaseType = Union[str, Sequence[str], Dataset, DataArray] -ThresholdedDict = Dict[str, Union[Dict]] # Dict === InFileDictionary -InFileLike = Union[ThresholdedDict, InFileBaseType, Dict[str, InFileBaseType]] - -FrequencyLike = Union[str, List[Union[str, Tuple, int]], Tuple[str, Union[List, Tuple]]] -# MonthsIndexer format: [12,1,2,3] -MonthsIndexer = Dict[Literal["month"], Sequence[int]] -# DatesIndexer format: ("01-25", "02-28") -DatesIndexer = Dict[Literal["date_bounds"], Tuple[str, str]] -Indexer = Union[MonthsIndexer, DatesIndexer] - -SamplingMethodLike = Literal["groupby", "resample", "groupby_ref_and_resample_study"] - -ThresholdValueType = Union[ - str, float, int, Dataset, DataArray, Sequence[Union[float, int, str]], None -] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7f97fffb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,144 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "icclim" +authors = [ + {name = "Christian Page", email = "christian.page@cerfacs.fr"} +] +maintainers = [ + {name = "Christian Page", email = "christian.page@cerfacs.fr"}, + {name = "Abel Aoun", email = "aoun.abel@gmail.com"} +] +readme = "README.rst" +license = {file = "LICENSE"} +classifiers = [ + "Programming Language :: Python", + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.9", + "Topic :: Scientific/Engineering :: Atmospheric Science" +] +dynamic = ["version", "description"] +requires-python = ">=3.9" +keywords = ["netcdf", "climate", "climate-indices", "climate-indicators", "xarray"] +dependencies = [ + "numpy>=1.16", + "xarray>=2022.6", + "xclim>=0.45, <=0.47", + "cf_xarray>=0.7.4", + "cftime>=1.4.1", + "dask[array]", + "netCDF4>=1.5.7", + "psutil", + "zarr", + "rechunker>=0.3, !=0.4", + "fsspec", + "pandas>=1.3", + "dateparser", + "pint", + "jinja2", + "psutil" +] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-cov" +] +dev = [ + "flit", + "ruff", + "pip", + "pre-commit>=2.9" +] +doc = [ + "sphinx", + "sphinx_codeautolink", + "sphinx_copybutton", + "sphinx_lfs_content", + "pydata-sphinx-theme" +] + +[project.urls] +Documentation = "https://icclim.readthedocs.io/en/latest/how_to/index.html" +Source = "https://github.com/cerfacs-globc/icclim/" + +[tool.ruff.format] +# Enable reformatting of code snippets in docstrings. +docstring-code-format = true + +[tool.ruff.lint] +select = [ + "F", # Pyflakes + "E", # pycodestyle error + "W", # pycodestyle warn + "C90", # mccabe + "I", # isort + # "N", # pep8-naming, to add + # "D", # pydocstyle, to add + "UP", # pyupgrade + "YTT", # flake8-2020 + # "ANN", # flake8-annotations, to add + "ASYNC", # flake8-async + # "S", # flake8-bandit + "BLE", # flake8-blind-except + # "FBT", # flake8-boolean-trap, to add + "B", # flake8-bugbear + "A", # flake8-builtins + "COM", # flake8-commas + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "T10", # flake8-debugger + "DJ", # flake8-django + "EM", # flake8-errmsg + "EXE", # flake8-executable + "FA", # flake8-future-annotations + "ISC", # flake8-implicit-str-concat + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "INP", # flake8-no-pep420 + "PIE", # flake8-pie + "T20", # flake8-print + "PYI", # flake8-pyi + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RSE", # flake8-raise + "RET", # flake8-return + "SLF", # flake8-self + "SLOT", # flake8-slots + "SIM", # flake8-simplify + "TID", # flake8-tidy-imports + "TCH", # flake8-type-checking + "INT", # flake8-gettext + "ARG", # flake8-unused-arguments + "PTH", # flake8-use-pathlib + "TD", # flake8-todos + "ERA", # eradicate + # "PD", # pandas-vet, not really compatible with xarray + "PGH", # pygrep-hooks + "PL", # Pylint + "TRY", # tryceratops + "FLY", # flynt + "NPY", # NumPy-specific rules + "AIR", # Airflow + "PERF", # Perflint + # "FURB", # refurb, preview + # "LOG", # flake8-logging, preview + "RUF" # Ruff-specific rules +] +ignore = [ + "PLR0913", # too many arguments in function, to add + "PLR2004", # Magic values, to add + "SLF001", # private member accessed, to add + "PLR0915", # Too many statement, to add + "ISC001", # String concat on same line, Managed by ruff format + "COM812" # No comma at the end, Managed by ruff format +] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2651b813..00000000 --- a/requirements.txt +++ /dev/null @@ -1,22 +0,0 @@ -cf_xarray>=0.7.4 -cftime -dask[array] -dateparser -distributed -fsspec -jinja2 -netCDF4~=1.5.7 -numpy -pandas>=1.3 -pint<0.20 -psutil -pytest -pyyaml -rechunker>=0.3.3 -setuptools -sphinx_codeautolink -sphinx_copybutton -sphinx_lfs_content -xarray>=2022.6 -xclim>=0.45 -zarr diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index c0bcd80f..00000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,29 +0,0 @@ -black -cf_xarray>=0.7.4 -cftime~=1.5.0 -dask -dateparser -flake8 -flake8-rst-docstrings -isort -netCDF4~=1.5.7 -numpy~=1.22 -pandas>=1.3 -pint<0.20 -pip -pre-commit>=2.9 -psutil -pydata-sphinx-theme -pylint -pytest -pytest-cov -rechunker>=0.3.3 -setuptools>=49.6.0 -sphinx -sphinx_codeautolink -sphinx_copybutton -sphinx_lfs_content -twine -xarray>=2022.6 -xclim>=0.45 -zarr diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 6b2f5a9d..00000000 --- a/setup.cfg +++ /dev/null @@ -1,42 +0,0 @@ -[flake8] -max-line-length = 88 -exclude = - .git, - docs, - build, - .eggs, - conf.py, -# ignores -# W503 line break before binary operator, black mess with it -# E203 whitespace before ':' - doesn't work well with black -ignore = - W503 - E203 -per-file-ignores = - tests/*:E402 - */__init__.py: F401 -rst-roles = - mod - ref - py:class -[tool:pylint] -ignore = docs,tests -disable = - bad-continuation, - invalid-name, - line-too-long, - protected-access, - too-few-public-methods, - too-many-arguments, - -[tool:pytest] -python_files = test_*.py -testpaths = icclim/tests - -[isort] -profile = black -known_first_party = icclim -skip = conf.py - -[coverage:run] -omit = */tests/*.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 71d84988..00000000 --- a/setup.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python - -from setuptools import find_packages, setup - -MINIMAL_REQUIREMENTS = [ - "numpy>=1.16", - "xarray>=2022.6", - "xclim>=0.45, <=0.47", - "cf_xarray>=0.7.4", - "cftime>=1.4.1", - "dask[array]", - "netCDF4>=1.5.7", - "pyyaml", - "psutil", - "zarr", - "rechunker>=0.3, !=0.4", - "fsspec", - "pandas>=1.3", - "dateparser", -] - -setup( - name="icclim", - version="6.5.0", - packages=find_packages(), - author="Christian P.", - author_email="christian.page@cerfacs.fr", - description="Python library for climate indices calculation", - long_description=open("README.rst").read(), - long_description_content_type="text/x-rst", - include_package_data=True, - url="https://github.com/cerfacs-globc/icclim", - install_requires=MINIMAL_REQUIREMENTS, - python_requires=">=3.9", - classifiers=[ - "Programming Language :: Python", - "Development Status :: 4 - Beta", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: French", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.9", - "Topic :: Scientific/Engineering :: Atmospheric Science", - ], -) diff --git a/icclim/.gitignore b/src/icclim/.gitignore similarity index 100% rename from icclim/.gitignore rename to src/icclim/.gitignore diff --git a/icclim/__init__.py b/src/icclim/__init__.py similarity index 71% rename from icclim/__init__.py rename to src/icclim/__init__.py index 301cba93..181758fb 100644 --- a/icclim/__init__.py +++ b/src/icclim/__init__.py @@ -1,7 +1,8 @@ -from icclim._generated_api import * # noqa +"""Python library for climate indices calculation""" + +from icclim._generated_api import * # noqa: F403 (add api to be in icclim module) from icclim.generic_indices.threshold import build_threshold from icclim.main import index, indice, indices -from icclim.models.constants import ICCLIM_VERSION from icclim.pre_processing.rechunk import create_optimized_zarr_store __all__ = [ @@ -15,4 +16,4 @@ "build_threshold", ] -__version__ = ICCLIM_VERSION +__version__ = "6.6.0" diff --git a/icclim/_generated_api.py b/src/icclim/_generated_api.py similarity index 91% rename from icclim/_generated_api.py rename to src/icclim/_generated_api.py index 5410b1c6..cf33247a 100644 --- a/icclim/_generated_api.py +++ b/src/icclim/_generated_api.py @@ -1,24 +1,30 @@ +# ruff: noqa: A001, E501 """ +icclim's API for ECAD indices and generic indices. + This module has been auto-generated. To modify these, edit the extractor tool in `tools/extract-icclim-funs.py`. This module exposes each climate index as individual functions for convenience. """ -# flake8: noqa E501 from __future__ import annotations -from datetime import datetime -from typing import Sequence - -from xarray.core.dataset import Dataset +from typing import TYPE_CHECKING import icclim from icclim.generic_indices.threshold import Threshold, build_threshold -from icclim.icclim_logger import Verbosity -from icclim.icclim_types import InFileLike, SamplingMethodLike -from icclim.models.frequency import Frequency, FrequencyLike -from icclim.models.netcdf_version import NetcdfVersion -from icclim.models.quantile_interpolation import QuantileInterpolation -from icclim.models.user_index_dict import UserIndexDict + +if TYPE_CHECKING: + import datetime as dt + from collections.abc import Sequence + + from xarray.core.dataset import Dataset + + from icclim.icclim_logger import Verbosity + from icclim.icclim_types import FrequencyLike, InFileLike, SamplingMethodLike + from icclim.models.frequency import Frequency + from icclim.models.netcdf_version import NetcdfVersion + from icclim.models.quantile_interpolation import QuantileInterpolation + from icclim.models.user_index_dict import UserIndexDict __all__ = [ "count_occurrences", @@ -109,7 +115,7 @@ def count_occurrences( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -119,30 +125,31 @@ def count_occurrences( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """count_occurrences + Count occurrences where threshold(s) are met (e.g. SU, Tx90p, RR1). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -162,12 +169,13 @@ def count_occurrences( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -202,7 +210,7 @@ def max_consecutive_occurrence( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -212,30 +220,31 @@ def max_consecutive_occurrence( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """max_consecutive_occurrence + Count the maximum number of consecutive occurrences when threshold(s) are met (e.g. CDD, CSU, CWD). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -255,12 +264,13 @@ def max_consecutive_occurrence( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -295,7 +305,7 @@ def sum_of_spell_lengths( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -306,30 +316,31 @@ def sum_of_spell_lengths( date_event: bool = False, min_spell_length: int | None = 6, ) -> Dataset: - """ + """sum_of_spell_lengths + Sum the lengths of each consecutive occurrence spell when threshold(s) are met. The minimum spell length is controlled by `min_spell_length` (e.g. WSDI, CSDI). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -349,12 +360,13 @@ def sum_of_spell_lengths( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -363,8 +375,8 @@ def sum_of_spell_lengths( stored in coordinates variables. **warning** This option may significantly slow down computation. min_spell_length: int - ``optional`` Minimum spell duration to be taken into account when computing the - sum_of_spell_lengths. + ``optional`` Minimum spell duration to be taken into account when computing + the sum_of_spell_lengths. Notes ----- @@ -393,7 +405,7 @@ def excess( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -403,30 +415,31 @@ def excess( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """excess + Compute the excess over the given threshold. The excess is `sum(x[x>t] - t)` where x is the studied variable and t the threshold (e.g. GD4). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -446,12 +459,13 @@ def excess( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -486,7 +500,7 @@ def deficit( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -496,30 +510,31 @@ def deficit( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """deficit + Compute the deficit below the given threshold. The deficit is `sum(t - x[x Dataset: - """ + """fraction_of_total + Compute the fraction of values meeting threshold(s) over the sum of every values (e.g. R75pTOT, R95pTOT). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -632,12 +649,13 @@ def fraction_of_total( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -672,7 +690,7 @@ def maximum( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -682,30 +700,31 @@ def maximum( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """maximum + Maximum of values that met threshold(s), if threshold(s) are given (e.g. Txx, Tnx). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -725,12 +744,13 @@ def maximum( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -765,7 +785,7 @@ def minimum( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -775,30 +795,31 @@ def minimum( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """minimum + Minimum of values that met threshold(s), if threshold(s) are given (e.g. Txn, Tnn). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -818,12 +839,13 @@ def minimum( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -858,7 +880,7 @@ def average( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -868,30 +890,31 @@ def average( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """average + Average of values that met threshold(s), if threshold(s) are given (e.g. Tx, Tn) Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -911,12 +934,13 @@ def average( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -951,7 +975,7 @@ def sum( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -961,30 +985,31 @@ def sum( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """sum + Sum of values that met threshold(s), if threshold(s) are given (e.g. PRCPTOT, RR). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1004,12 +1029,13 @@ def sum( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1044,7 +1070,7 @@ def standard_deviation( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1054,30 +1080,31 @@ def standard_deviation( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """standard_deviation + Standard deviation of values that met threshold(s), if threshold(s) are given. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1097,12 +1124,13 @@ def standard_deviation( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1137,7 +1165,7 @@ def max_of_rolling_sum( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1148,30 +1176,31 @@ def max_of_rolling_sum( date_event: bool = False, rolling_window_width: int | None = 5, ) -> Dataset: - """ + """max_of_rolling_sum + Maximum of rolling sum over time dimension (e.g. RX5DAY: maximum 5 days window of precipitation accumulation). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1191,12 +1220,13 @@ def max_of_rolling_sum( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1206,7 +1236,7 @@ def max_of_rolling_sum( **warning** This option may significantly slow down computation. rolling_window_width: int ``optional`` Window width of the rolling window for indicators such as - `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` # noqa + `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` Notes ----- @@ -1235,7 +1265,7 @@ def min_of_rolling_sum( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1246,30 +1276,31 @@ def min_of_rolling_sum( date_event: bool = False, rolling_window_width: int | None = 5, ) -> Dataset: - """ + """min_of_rolling_sum + Minimum of rolling sum over time dimension. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1289,12 +1320,13 @@ def min_of_rolling_sum( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1304,7 +1336,7 @@ def min_of_rolling_sum( **warning** This option may significantly slow down computation. rolling_window_width: int ``optional`` Window width of the rolling window for indicators such as - `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` # noqa + `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` Notes ----- @@ -1333,7 +1365,7 @@ def max_of_rolling_average( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1344,30 +1376,31 @@ def max_of_rolling_average( date_event: bool = False, rolling_window_width: int | None = 5, ) -> Dataset: - """ + """max_of_rolling_average + Maximum of rolling average over time dimension. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1387,12 +1420,13 @@ def max_of_rolling_average( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1402,7 +1436,7 @@ def max_of_rolling_average( **warning** This option may significantly slow down computation. rolling_window_width: int ``optional`` Window width of the rolling window for indicators such as - `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` # noqa + `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` Notes ----- @@ -1431,7 +1465,7 @@ def min_of_rolling_average( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1442,30 +1476,31 @@ def min_of_rolling_average( date_event: bool = False, rolling_window_width: int | None = 5, ) -> Dataset: - """ + """min_of_rolling_average + Minimum of rolling average over time dimension. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1485,12 +1520,13 @@ def min_of_rolling_average( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1500,7 +1536,7 @@ def min_of_rolling_average( **warning** This option may significantly slow down computation. rolling_window_width: int ``optional`` Window width of the rolling window for indicators such as - `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` # noqa + `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` Notes ----- @@ -1529,7 +1565,7 @@ def mean_of_difference( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1539,30 +1575,31 @@ def mean_of_difference( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """mean_of_difference + Average of the difference between two variables, or one variable and it's reference period values (e.g. DTR: `mean(tasmax - tasmin)`). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1582,12 +1619,13 @@ def mean_of_difference( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1622,7 +1660,7 @@ def difference_of_extremes( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1632,30 +1670,31 @@ def difference_of_extremes( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """difference_of_extremes + Difference of extremes between two variables, or one variable and it's reference period values. The extremes are always `maximum` for the first variable and `minimum` for the second variable (e.g. ETR: `max(tasmax) - min(tasmin)`). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1675,12 +1714,13 @@ def difference_of_extremes( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1715,7 +1755,7 @@ def mean_of_absolute_one_time_step_difference( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1725,30 +1765,31 @@ def mean_of_absolute_one_time_step_difference( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ + """mean_of_absolute_one_time_step_difference + Average of the absolute one time step by one time step difference between two variables, or one variable and it's reference period values (e.g. vDTR: `mean((tasmax[i] - tasmin[i]) - (tasmax[i-1] - tasmin[i-1])` ; where i is the day of measure). Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1768,12 +1809,13 @@ def mean_of_absolute_one_time_step_difference( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1808,7 +1850,7 @@ def difference_of_means( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, ignore_Feb29th: bool = False, @@ -1819,30 +1861,31 @@ def difference_of_means( date_event: bool = False, sampling_method: SamplingMethodLike = "resample", ) -> Dataset: - """ + """difference_of_means + Difference of the average between two variables, or one variable and it's reference period values (e.g. anomaly: `mean(tasmax) - mean(tasmax_ref]))`. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1862,12 +1905,13 @@ def difference_of_means( ignore_Feb29th: bool ``optional`` Ignoring or not February 29th (default: False). out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -1878,7 +1922,8 @@ def difference_of_means( sampling_method: str Choose whether the output sampling configured in `slice_mode` is a `groupby` operation or a `resample` operation (as per xarray definitions). - Possible values: ``{"groupby", "resample", "groupby_ref_and_resample_study"}`` + Possible values: + ``{"groupby", "resample", "groupby_ref_and_resample_study"}`` (default: "resample") `groupby_ref_and_resample_study` may only be used when computing the `difference_of_means` (a.k.a the anomaly). @@ -1910,38 +1955,39 @@ def tg( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TG: Mean of daily mean temperature + """TG + + Mean of daily mean temperature Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -1989,38 +2035,39 @@ def tn( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TN: Mean of daily minimum temperature + """TN + + Mean of daily minimum temperature Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2068,38 +2115,39 @@ def tx( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TX: Mean of daily maximum temperature + """TX + + Mean of daily maximum temperature Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2147,38 +2195,39 @@ def dtr( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - DTR: Mean Diurnal Temperature Range + """DTR + + Mean Diurnal Temperature Range Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2226,38 +2275,39 @@ def etr( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - ETR: Intra-period extreme temperature range + """ETR + + Intra-period extreme temperature range Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2305,38 +2355,39 @@ def vdtr( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - vDTR: Mean day-to-day variation in Diurnal Temperature Range + """vDTR + + Mean day-to-day variation in Diurnal Temperature Range Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2384,38 +2435,39 @@ def su( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SU: Number of Summer Days (Tmax > 25C) + """SU + + Number of Summer Days (Tmax > 25C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2466,38 +2518,39 @@ def tr( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TR: Number of Tropical Nights (Tmin > 20C) + """TR + + Number of Tropical Nights (Tmin > 20C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2548,9 +2601,9 @@ def wsdi( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -2559,31 +2612,32 @@ def wsdi( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - WSDI: Warm-spell duration index (days) + """WSDI + + Warm-spell duration index (days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2595,7 +2649,7 @@ def wsdi( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -2621,8 +2675,8 @@ def wsdi( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -2666,9 +2720,9 @@ def tg90p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -2677,31 +2731,32 @@ def tg90p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TG90p: Days when Tmean > 90th percentile + """TG90p + + Days when Tmean > 90th percentile Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2713,7 +2768,7 @@ def tg90p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -2739,8 +2794,8 @@ def tg90p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -2784,9 +2839,9 @@ def tn90p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -2795,31 +2850,32 @@ def tn90p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TN90p: Days when Tmin > 90th percentile + """TN90p + + Days when Tmin > 90th percentile Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2831,7 +2887,7 @@ def tn90p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -2857,8 +2913,8 @@ def tn90p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -2902,9 +2958,9 @@ def tx90p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -2913,31 +2969,32 @@ def tx90p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TX90p: Days when Tmax > 90th daily percentile + """TX90p + + Days when Tmax > 90th daily percentile Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -2949,7 +3006,7 @@ def tx90p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -2975,8 +3032,8 @@ def tx90p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -3020,38 +3077,39 @@ def txx( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TXx: Maximum daily maximum temperature + """TXx + + Maximum daily maximum temperature Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3099,38 +3157,39 @@ def tnx( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TNx: Maximum daily minimum temperature + """TNx + + Maximum daily minimum temperature Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3178,38 +3237,39 @@ def csu( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - CSU: Maximum number of consecutive summer days (Tmax >25 C) + """CSU + + Maximum number of consecutive summer days (Tmax >25 C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3260,38 +3320,39 @@ def gd4( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - GD4: Growing degree days (sum of Tmean > 4 C) + """GD4 + + Growing degree days (sum of Tmean > 4 C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3342,38 +3403,39 @@ def fd( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - FD: Number of Frost Days (Tmin < 0C) + """FD + + Number of Frost Days (Tmin < 0C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3424,38 +3486,39 @@ def cfd( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - CFD: Maximum number of consecutive frost days (Tmin < 0 C) + """CFD + + Maximum number of consecutive frost days (Tmin < 0 C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3506,38 +3569,39 @@ def hd17( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - HD17: Heating degree days (sum of Tmean < 17 C) + """HD17 + + Heating degree days (sum of Tmean < 17 C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3588,38 +3652,39 @@ def id( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - ID: Number of sharp Ice Days (Tmax < 0C) + """ID + + Number of sharp Ice Days (Tmax < 0C) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3670,9 +3735,9 @@ def tg10p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -3681,31 +3746,32 @@ def tg10p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TG10p: Days when Tmean < 10th percentile + """TG10p + + Days when Tmean < 10th percentile Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3717,7 +3783,7 @@ def tg10p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -3743,8 +3809,8 @@ def tg10p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -3788,9 +3854,9 @@ def tn10p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -3799,31 +3865,32 @@ def tn10p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TN10p: Days when Tmin < 10th percentile + """TN10p + + Days when Tmin < 10th percentile Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3835,7 +3902,7 @@ def tn10p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -3861,8 +3928,8 @@ def tn10p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -3906,9 +3973,9 @@ def tx10p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -3917,31 +3984,32 @@ def tx10p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TX10p: Days when Tmax < 10th percentile + """TX10p + + Days when Tmax < 10th percentile Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -3953,7 +4021,7 @@ def tx10p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -3979,8 +4047,8 @@ def tx10p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -4024,38 +4092,39 @@ def txn( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TXn: Minimum daily maximum temperature + """TXn + + Minimum daily maximum temperature Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4103,38 +4172,39 @@ def tnn( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - TNn: Minimum daily minimum temperature + """TNn + + Minimum daily minimum temperature Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4182,9 +4252,9 @@ def csdi( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -4193,31 +4263,32 @@ def csdi( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - CSDI: Cold-spell duration index (days) + """CSDI + + Cold-spell duration index (days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4229,7 +4300,7 @@ def csdi( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -4255,8 +4326,8 @@ def csdi( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -4300,38 +4371,39 @@ def cdd( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - CDD: Maximum consecutive dry days (Precip < 1mm) + """CDD + + Maximum consecutive dry days (Precip < 1mm) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4382,38 +4454,39 @@ def prcptot( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - PRCPTOT: Total precipitation during Wet Days + """PRCPTOT + + Total precipitation during Wet Days Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4464,38 +4537,39 @@ def rr1( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - RR1: Number of Wet Days (precip >= 1 mm) + """RR1 + + Number of Wet Days (precip >= 1 mm) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4546,38 +4620,39 @@ def sdii( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SDII: Average precipitation during Wet Days (SDII) + """SDII + + Average precipitation during Wet Days (SDII) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4628,38 +4703,39 @@ def cwd( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - CWD: Maximum consecutive wet days (Precip >= 1mm) + """CWD + + Maximum consecutive wet days (Precip >= 1mm) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4710,38 +4786,39 @@ def rr( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - RR: Precipitation sum (mm) + """RR + + Precipitation sum (mm) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4789,38 +4866,39 @@ def r10mm( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R10mm: Number of heavy precipitation days (Precip >=10mm) + """R10mm + + Number of heavy precipitation days (Precip >=10mm) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4871,38 +4949,39 @@ def r20mm( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R20mm: Number of very heavy precipitation days (Precip >= 20mm) + """R20mm + + Number of very heavy precipitation days (Precip >= 20mm) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -4953,38 +5032,39 @@ def rx1day( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - RX1day: maximum 1-day total precipitation + """RX1day + + maximum 1-day total precipitation Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5032,38 +5112,39 @@ def rx5day( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - RX5day: maximum 5-day total precipitation + """RX5day + + maximum 5-day total precipitation Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5111,9 +5192,9 @@ def r75p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -5122,31 +5203,32 @@ def r75p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R75p: Days with RR > 75th percentile of daily amounts (moderate wet days) (d) + """R75p + + Days with RR > 75th percentile of daily amounts (moderate wet days) (d) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5158,7 +5240,7 @@ def r75p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -5184,8 +5266,8 @@ def r75p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -5230,9 +5312,9 @@ def r75ptot( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -5241,31 +5323,32 @@ def r75ptot( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R75pTOT: Precipitation fraction due to moderate wet days (> 75th percentile) + """R75pTOT + + Precipitation fraction due to moderate wet days (> 75th percentile) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5277,7 +5360,7 @@ def r75ptot( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -5303,8 +5386,8 @@ def r75ptot( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -5349,9 +5432,9 @@ def r95p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -5360,31 +5443,32 @@ def r95p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R95p: Days with RR > 95th percentile of daily amounts (very wet days) (days) + """R95p + + Days with RR > 95th percentile of daily amounts (very wet days) (days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5396,7 +5480,7 @@ def r95p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -5422,8 +5506,8 @@ def r95p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -5468,9 +5552,9 @@ def r95ptot( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -5479,31 +5563,32 @@ def r95ptot( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R95pTOT: Precipitation fraction due to very wet days (> 95th percentile) + """R95pTOT + + Precipitation fraction due to very wet days (> 95th percentile) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5515,7 +5600,7 @@ def r95ptot( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -5541,8 +5626,8 @@ def r95ptot( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -5587,9 +5672,9 @@ def r99p( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -5598,31 +5683,32 @@ def r99p( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R99p: Days with RR > 99th percentile of daily amounts (extremely wet days) + """R99p + + Days with RR > 99th percentile of daily amounts (extremely wet days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5634,7 +5720,7 @@ def r99p( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -5660,8 +5746,8 @@ def r99p( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -5706,9 +5792,9 @@ def r99ptot( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -5717,31 +5803,32 @@ def r99ptot( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - R99pTOT: Precipitation fraction due to extremely wet days (> 99th percentile) + """R99pTOT + + Precipitation fraction due to extremely wet days (> 99th percentile) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5753,7 +5840,7 @@ def r99ptot( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -5779,8 +5866,8 @@ def r99ptot( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -5825,38 +5912,39 @@ def sd( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SD: Mean of daily snow depth + """SD + + Mean of daily snow depth Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5904,38 +5992,39 @@ def sd1( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SD1: Snow days (SD >= 1 cm) + """SD1 + + Snow days (SD >= 1 cm) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -5986,38 +6075,39 @@ def sd5cm( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SD5cm: Number of days with snow depth >= 5 cm + """SD5cm + + Number of days with snow depth >= 5 cm Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6068,38 +6158,39 @@ def sd50cm( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SD50cm: Number of days with snow depth >= 50 cm + """SD50cm + + Number of days with snow depth >= 50 cm Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6150,9 +6241,9 @@ def cd( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -6161,31 +6252,32 @@ def cd( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - CD: Days with TG < 25th percentile of daily mean temperature and RR <25th percentile of daily precipitation sum (cold/dry days) + """CD + + Days with TG < 25th percentile of daily mean temperature and RR <25th percentile of daily precipitation sum (cold/dry days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6197,7 +6289,7 @@ def cd( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -6223,8 +6315,8 @@ def cd( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -6278,9 +6370,9 @@ def cw( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -6289,31 +6381,32 @@ def cw( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - CW: Days with TG < 25th percentile of daily mean temperature and RR >75th percentile of daily precipitation sum (cold/wet days) + """CW + + Days with TG < 25th percentile of daily mean temperature and RR >75th percentile of daily precipitation sum (cold/wet days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6325,7 +6418,7 @@ def cw( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -6351,8 +6444,8 @@ def cw( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -6406,9 +6499,9 @@ def wd( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -6417,31 +6510,32 @@ def wd( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - WD: Days with TG > 75th percentile of daily mean temperature and RR <25th percentile of daily precipitation sum (warm/dry days) + """WD + + Days with TG > 75th percentile of daily mean temperature and RR <25th percentile of daily precipitation sum (warm/dry days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6453,7 +6547,7 @@ def wd( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -6479,8 +6573,8 @@ def wd( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -6534,9 +6628,9 @@ def ww( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, only_leap_years: bool = False, ignore_Feb29th: bool = False, interpolation: str | QuantileInterpolation = "median_unbiased", @@ -6545,31 +6639,32 @@ def ww( logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - WW: Days with TG > 75th percentile of daily mean temperature and RR >75th percentile of daily precipitation sum (warm/wet days) + """WW + + Days with TG > 75th percentile of daily mean temperature and RR >75th percentile of daily precipitation sum (warm/wet days) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6581,7 +6676,7 @@ def ww( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -6607,8 +6702,8 @@ def ww( netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -6662,38 +6757,39 @@ def fxx( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - FXx: Maximum value of daily maximum wind gust + """FXx + + Maximum value of daily maximum wind gust Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6741,38 +6837,39 @@ def fg6bft( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - FG6Bft: Days with daily averaged wind ≥ 6 Bft (10.8 m s-1) + """FG6Bft + + Days with daily averaged wind ≥ 6 Bft (10.8 m s-1) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6823,38 +6920,39 @@ def fgcalm( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - FGcalm: Calm days, days with daily averaged wind <= 2 m s-1 + """FGcalm + + Calm days, days with daily averaged wind <= 2 m s-1 Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6905,38 +7003,39 @@ def fg( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - FG: Mean of daily mean wind strength + """FG + + Mean of daily mean wind strength Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -6984,38 +7083,39 @@ def ddnorth( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - DDnorth: Days with northerly winds (DD > 315° or DD ≤ 45°) + """DDnorth + + Days with northerly winds (DD > 315° or DD ≤ 45°) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7066,38 +7166,39 @@ def ddeast( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - DDeast: Days with easterly winds (45° < DD <= 135°) + """DDeast + + Days with easterly winds (45° < DD <= 135°) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7148,38 +7249,39 @@ def ddsouth( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - DDsouth: Days with southerly winds (135° < DD <= 225°) + """DDsouth + + Days with southerly winds (135° < DD <= 225°) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7230,38 +7332,39 @@ def ddwest( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - DDwest: Days with westerly winds (225° < DD <= 315°) + """DDwest + + Days with westerly winds (225° < DD <= 315°) Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7312,38 +7415,39 @@ def gsl( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - GSL: Growing season length + """GSL + + Growing season length Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7391,39 +7495,40 @@ def spi6( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SPI6: 6-Month Standardized Precipitation Index + """SPI6 + + 6-Month Standardized Precipitation Index Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7435,7 +7540,7 @@ def spi6( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -7486,39 +7591,40 @@ def spi3( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, ignore_Feb29th: bool = False, netcdf_version: str | NetcdfVersion = "NETCDF4", logs_verbosity: Verbosity | str = "LOW", date_event: bool = False, ) -> Dataset: - """ - SPI3: 3-Month Standardized Precipitation Index + """SPI3 + + 3-Month Standardized Precipitation Index Source: ECA&D, Algorithm Theoretical Basis Document (ATBD) v11. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7530,7 +7636,7 @@ def spi3( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -7582,9 +7688,9 @@ def custom_index( in_files: InFileLike, var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, doy_window_width: int = 5, only_leap_years: bool = False, ignore_Feb29th: bool = False, @@ -7598,31 +7704,32 @@ def custom_index( rolling_window_width: int | None = 5, sampling_method: SamplingMethodLike = "resample", ) -> Dataset: - """ - This function can be used to create indices using simple operators. - Use the `user_index` parameter to describe how the index should be computed. - You can find some examples in icclim documentation at :ref:`custom_indices` - Parameters - ---------- + """Compute custom indices using simple operators. + Use the `user_index` parameter to describe how the index should be computed. + You can find some examples in icclim documentation at :ref:`custom_indices` + + Parameters + ---------- in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -7634,7 +7741,7 @@ def custom_index( If the input ``in_files`` is a ``Dataset``, ``out_file`` field is ignored. Use the function returned value instead to retrieve the computed value. If ``out_file`` already exists, icclim will overwrite it! - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -7662,12 +7769,13 @@ def custom_index( Default is "median_unbiased", a.k.a type 8 or method 8. Ignored for non percentile based indices. out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). logs_verbosity: str | Verbosity ``optional`` Configure how verbose icclim is. Possible values: ``{"LOW", "HIGH", "SILENT"}`` (default: "LOW") @@ -7676,15 +7784,16 @@ def custom_index( stored in coordinates variables. **warning** This option may significantly slow down computation. min_spell_length: int - ``optional`` Minimum spell duration to be taken into account when computing the - sum_of_spell_lengths. + ``optional`` Minimum spell duration to be taken into account when computing + the sum_of_spell_lengths. rolling_window_width: int ``optional`` Window width of the rolling window for indicators such as - `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` # noqa + `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` sampling_method: str Choose whether the output sampling configured in `slice_mode` is a `groupby` operation or a `resample` operation (as per xarray definitions). - Possible values: ``{"groupby", "resample", "groupby_ref_and_resample_study"}`` + Possible values: + ``{"groupby", "resample", "groupby_ref_and_resample_study"}`` (default: "resample") `groupby_ref_and_resample_study` may only be used when computing the `difference_of_means` (a.k.a the anomaly). diff --git a/icclim/ecad/__init__.py b/src/icclim/ecad/__init__.py similarity index 100% rename from icclim/ecad/__init__.py rename to src/icclim/ecad/__init__.py diff --git a/icclim/ecad/ecad_indices.py b/src/icclim/ecad/ecad_indices.py similarity index 98% rename from icclim/ecad/ecad_indices.py rename to src/icclim/ecad/ecad_indices.py index ab6ea5cd..ed2727dd 100644 --- a/icclim/ecad/ecad_indices.py +++ b/src/icclim/ecad/ecad_indices.py @@ -17,17 +17,16 @@ class EcadIndexRegistry(Registry[StandardIndex]): _item_class = StandardIndex - # TODO Add indices of wind gust, wind direction, - # radiation, pressure, - # cloud cover, sunshine, - # humidity + # TODO @bzah: Add indices of wind gust, wind direction, + # radiation, pressure, cloud cover, sunshine, humidity + # https://github.com/cerfacs-globc/icclim/issues/289 @staticmethod def get_item_aliases(item: StandardIndex) -> list[str]: return [item.short_name] @classmethod - def list(cls: EcadIndexRegistry) -> list[str]: + def to_list(cls: EcadIndexRegistry) -> list[str]: return [ f"{i.group.name} | {i.short_name} | {i.definition}" for i in cls.values() ] @@ -461,8 +460,8 @@ def list(cls: EcadIndexRegistry) -> list[str]: indicator=GenericIndicatorRegistry.CountOccurrences, threshold=build_threshold("> 75 period_per", threshold_min_value="1 mm/day"), output_unit="day", - definition="Days with RR > 75th percentile of daily amounts (moderate wet days)" - " (d)", + definition="Days with RR > 75th percentile of daily amounts" + " (moderate wet days) (d)", source=ECAD_ATBD, short_name="R75p", group=IndexGroupRegistry.RAIN, @@ -487,8 +486,8 @@ def list(cls: EcadIndexRegistry) -> list[str]: indicator=GenericIndicatorRegistry.CountOccurrences, threshold=build_threshold("> 95 period_per", threshold_min_value="1 mm/day"), output_unit="day", - definition="Days with RR > 95th percentile of daily amounts (very wet days)" - " (days)", + definition="Days with RR > 95th percentile of daily amounts" + " (very wet days) (days)", source=ECAD_ATBD, short_name="R95p", group=IndexGroupRegistry.RAIN, @@ -577,7 +576,7 @@ def list(cls: EcadIndexRegistry) -> list[str]: group=IndexGroupRegistry.SNOW, input_variables=[StandardVariableRegistry.SND], ) - # Compound (precipitation and temperature) + # Compound (precipitation and temperature) # noqa: ERA001 (false positive) CD = StandardIndex( reference=ECAD_REFERENCE, indicator=GenericIndicatorRegistry.CountOccurrences, diff --git a/icclim/ecad/xclim_binding.py b/src/icclim/ecad/xclim_binding.py similarity index 78% rename from icclim/ecad/xclim_binding.py rename to src/icclim/ecad/xclim_binding.py index d170cc1f..f79677a2 100644 --- a/icclim/ecad/xclim_binding.py +++ b/src/icclim/ecad/xclim_binding.py @@ -1,6 +1,7 @@ from __future__ import annotations -import xarray +from typing import TYPE_CHECKING + import xclim from icclim.generic_indices.generic_indicators import ( @@ -10,7 +11,11 @@ ) from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.frequency import FrequencyRegistry -from icclim.models.index_config import IndexConfig + +if TYPE_CHECKING: + import xarray + + from icclim.models.index_config import IndexConfig class XCLIM_BINDING: @@ -36,11 +41,11 @@ def __call__(self, config: IndexConfig) -> xarray.DataArray: def preprocess(self, *args, **kwargs) -> list[xarray.DataArray]: """Not implemented as xclim indicator already handle pre/post processing""" - raise NotImplementedError() + raise NotImplementedError def postprocess(self, *args, **kwargs) -> xarray.DataArray: """Not implemented as xclim indicator already handle pre/post processing""" - raise NotImplementedError() + raise NotImplementedError class StandardizedPrecipitationIndex3(Indicator): """ @@ -55,21 +60,25 @@ class StandardizedPrecipitationIndex3(Indicator): def __call__(self, config: IndexConfig) -> xarray.DataArray: if config.frequency is not FrequencyRegistry.YEAR: # year is default freq - raise InvalidIcclimArgumentError( - "`slice_mode` cannot be configured when computing SPI3" - ) + msg = "`slice_mode` cannot be configured when computing SPI3" + raise InvalidIcclimArgumentError(msg) study, ref = get_couple_of_var(config.climate_variables, "SPI") return xclim.atmos.standardized_precipitation_index( - pr=study, pr_cal=ref, freq="MS", window=3, dist="gamma", method="APP" + pr=study, + pr_cal=ref, + freq="MS", + window=3, + dist="gamma", + method="APP", ) def preprocess(self, *args, **kwargs) -> list[xarray.DataArray]: """Not implemented as xclim indicator already handle pre/post processing""" - raise NotImplementedError() + raise NotImplementedError def postprocess(self, *args, **kwargs) -> xarray.DataArray: """Not implemented as xclim indicator already handle pre/post processing""" - raise NotImplementedError() + raise NotImplementedError class StandardizedPrecipitationIndex6(Indicator): """ @@ -84,18 +93,22 @@ class StandardizedPrecipitationIndex6(Indicator): def __call__(self, config: IndexConfig) -> xarray.DataArray: if config.frequency is not FrequencyRegistry.YEAR: # year is default freq - raise InvalidIcclimArgumentError( - "`slice_mode` cannot be configured when computing SPI6" - ) + msg = "`slice_mode` cannot be configured when computing SPI6" + raise InvalidIcclimArgumentError(msg) study, ref = get_couple_of_var(config.climate_variables, "SPI") return xclim.atmos.standardized_precipitation_index( - pr=study, pr_cal=ref, freq="MS", window=6, dist="gamma", method="APP" + pr=study, + pr_cal=ref, + freq="MS", + window=6, + dist="gamma", + method="APP", ) def preprocess(self, *args, **kwargs) -> list[xarray.DataArray]: """Not implemented as xclim indicator already handle pre/post processing""" - raise NotImplementedError() + raise NotImplementedError def postprocess(self, *args, **kwargs) -> xarray.DataArray: """Not implemented as xclim indicator already handle pre/post processing""" - raise NotImplementedError() + raise NotImplementedError diff --git a/icclim/generic_indices/__init__.py b/src/icclim/generic_indices/__init__.py similarity index 100% rename from icclim/generic_indices/__init__.py rename to src/icclim/generic_indices/__init__.py diff --git a/icclim/generic_indices/generic_indicators.py b/src/icclim/generic_indices/generic_indicators.py similarity index 86% rename from icclim/generic_indices/generic_indicators.py rename to src/icclim/generic_indices/generic_indicators.py index ecc454b5..d815c119 100644 --- a/icclim/generic_indices/generic_indicators.py +++ b/src/icclim/generic_indices/generic_indicators.py @@ -1,14 +1,13 @@ from __future__ import annotations import abc -from abc import ABC -from datetime import timedelta +import contextlib +from abc import ABC, abstractmethod from functools import partial, reduce -from typing import Any, Callable +from typing import TYPE_CHECKING, Any, Callable from warnings import warn import jinja2 -import numpy import numpy as np import xarray as xr from jinja2 import Environment @@ -19,18 +18,21 @@ from xclim.core.calendar import select_time from xclim.core.cfchecks import cfcheck_from_name from xclim.core.datachecks import check_freq -from xclim.core.missing import MissingBase from xclim.core.options import MISSING_METHODS, MISSING_OPTIONS, OPTIONS -from xclim.core.units import convert_units_to, rate2amount, str2pint, to_agg_units +from xclim.core.units import ( + convert_units_to, + rate2amount, + str2pint, + to_agg_units, + units2pint, +) from xclim.core.units import units as xc_units -from xclim.core.units import units2pint from xclim.indices import run_length from icclim.generic_indices.generic_templates import INDICATORS_TEMPLATES_EN from icclim.generic_indices.threshold import PercentileThreshold, Threshold from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.cf_calendar import CfCalendarRegistry -from icclim.models.climate_variable import ClimateVariable from icclim.models.constants import ( GROUP_BY_METHOD, GROUP_BY_REF_AND_RESAMPLE_STUDY_METHOD, @@ -40,22 +42,32 @@ UNITS_KEY, ) from icclim.models.frequency import RUN_INDEXER, Frequency, FrequencyRegistry -from icclim.models.index_config import IndexConfig -from icclim.models.logical_link import LogicalLink from icclim.models.operator import OperatorRegistry from icclim.models.registry import Registry -jinja_env = Environment() +if TYPE_CHECKING: + from datetime import timedelta + + from xclim.core.missing import MissingBase + + from icclim.models.climate_variable import ClimateVariable + from icclim.models.index_config import IndexConfig + from icclim.models.logical_link import LogicalLink + +jinja_env = Environment(autoescape=False) class MissingMethodLike(metaclass=abc.ABCMeta): """workaround xclim missing type""" - # todo: PR that to xclim + # TODO @bzah: PR that to xclim + # https://github.com/cerfacs-globc/icclim/issues/289 + @abstractmethod def execute(self, *args, **kwargs) -> MissingBase: ... + @abstractmethod def validate(self, *args, **kwargs) -> bool: ... @@ -66,11 +78,11 @@ class Indicator(ABC): long_name: str cell_methods: str - templated_properties = [ + templated_properties = ( "standard_name", "long_name", "cell_methods", - ] + ) @abc.abstractmethod def __call__(self, *args, **kwargs) -> DataArray: @@ -95,10 +107,11 @@ def __init__(self, missing="from_context", missing_options=None): self.missing_options = missing_options self.missing = missing if self.missing == "from_context" and self.missing_options is not None: - raise ValueError( + err = ( "Cannot set `missing_options` with `missing` method being from context." ) - missing_method: MissingMethodLike = MISSING_METHODS[self.missing] # noqa typing + raise ValueError(err) + missing_method: MissingMethodLike = MISSING_METHODS[self.missing] # typing self._missing = missing_method.execute if self.missing_options: missing_method.validate(**self.missing_options) @@ -112,7 +125,7 @@ def preprocess( ) -> list[ClimateVariable]: _check_data(climate_vars, src_freq.pandas_freq) _check_cf(climate_vars) - self.format(jinja_scope=jinja_scope) + self.format_template(jinja_scope=jinja_scope) return climate_vars def postprocess( @@ -130,7 +143,7 @@ def postprocess( # reference variable is a subset of the studied variable, # so no need to check it. das = filter(lambda cv: not cv.is_reference, climate_vars) - das = map(lambda cv: cv.studied_data, das) + das = (cv.studied_data for cv in das) das = list(das) if "time" in result.dims: result = self._handle_missing_values( @@ -145,7 +158,7 @@ def postprocess( result.attrs["history"] = "" return result - def format(self, jinja_scope: dict): + def format_template(self, jinja_scope: dict): for templated_property in self.templated_properties: template = jinja_env.from_string( getattr(self, templated_property), @@ -164,7 +177,7 @@ def _handle_missing_values( options = self.missing_options or OPTIONS[MISSING_OPTIONS].get(self.missing, {}) # We flag periods according to the missing method. skip variables without a time # coordinate. - missing_method: MissingMethodLike = MISSING_METHODS[self.missing] # noqa typing + missing_method: MissingMethodLike = MISSING_METHODS[self.missing] # typing miss = ( missing_method.execute(da, resample_freq, src_freq, options, indexer) for da in in_data @@ -173,7 +186,7 @@ def _handle_missing_values( # Reduce by or and broadcast to ensure the same length in time # When indexing is used and there are no valid points in the last period, # mask will not include it - mask = reduce(np.logical_or, miss) # noqa typing + mask = reduce(np.logical_or, miss) # typing if isinstance(mask, DataArray) and mask.time.size < out_data.time.size: mask = mask.reindex(time=out_data.time, fill_value=True) return out_data.where(~mask) @@ -188,7 +201,7 @@ def __init__( check_vars: ( Callable[[list[ClimateVariable], GenericIndicator], None] | None ) = None, - sampling_methods: list[str] = None, + sampling_methods: list[str] | None = None, **kwargs, ): super().__init__(**kwargs) @@ -204,7 +217,7 @@ def __init__( sampling_methods if sampling_methods is not None else [RESAMPLE_METHOD] ) - def preprocess( # noqa signature != from super + def preprocess( self, climate_vars: list[ClimateVariable], jinja_scope: dict[str, Any], @@ -214,22 +227,12 @@ def preprocess( # noqa signature != from super coef: float | None, sampling_method: str, ) -> list[ClimateVariable]: - if not _same_freq_for_all(climate_vars): - raise InvalidIcclimArgumentError( - "All variables must have the same time frequency (for example daily) to" - " be compared with each others, but this was not the case." - ) - if self.check_vars is not None: - self.check_vars(climate_vars, self) - if sampling_method not in self.sampling_methods: - raise InvalidIcclimArgumentError( - f"{self.name} can only be computed with the following" - f" sampling_method(s): {self.sampling_methods}" - ) + self._check_for_invalid_setup(climate_vars, sampling_method) if output_unit is not None: if _is_amount_unit(output_unit): climate_vars = _convert_rates_to_amounts( - climate_vars=climate_vars, output_unit=output_unit + climate_vars=climate_vars, + output_unit=output_unit, ) elif _is_a_diff_indicator(self) and output_unit != "%": # [gh:255] Indicators computing the difference between two @@ -240,17 +243,18 @@ def preprocess( # noqa signature != from super # to a 15 degC and *is not* to a -258.15 degC. for climate_var in climate_vars: climate_var.studied_data = convert_units_to( - climate_var.studied_data, target=output_unit + climate_var.studied_data, + target=output_unit, ) - else: - pass # nothing to do if coef is not None: for climate_var in climate_vars: climate_var.studied_data = coef * climate_var.studied_data if output_frequency.indexer: for climate_var in climate_vars: climate_var.studied_data = select_time( - climate_var.studied_data, **output_frequency.indexer, drop=True + climate_var.studied_data, + **output_frequency.indexer, + drop=True, ) return super().preprocess( climate_vars=climate_vars, @@ -261,14 +265,17 @@ def preprocess( # noqa signature != from super def __call__(self, config: IndexConfig) -> DataArray: src_freq = config.climate_variables[0].source_frequency base_jinja_scope = { - "np": numpy, + "np": np, "enumerate": enumerate, "len": len, "output_freq": config.frequency, "source_freq": src_freq, } climate_vars_meta = _get_climate_vars_metadata( - config.climate_variables, src_freq, base_jinja_scope, jinja_env + config.climate_variables, + src_freq, + base_jinja_scope, + jinja_env, ) jinja_scope: dict[str, Any] = { "min_spell_length": config.min_spell_length, @@ -317,6 +324,26 @@ def __eq__(self, other) -> bool: and self.process == other.process ) + def _check_for_invalid_setup( + self, + climate_vars: list[ClimateVariable], + sampling_method: str, + ): + if not _same_freq_for_all(climate_vars): + msg = ( + "All variables must have the same time frequency (for example daily) to" + " be compared with each others, but this was not the case." + ) + raise InvalidIcclimArgumentError(msg) + if sampling_method not in self.sampling_methods: + msg = ( + f"{self.name} can only be computed with the following" + f" sampling_method(s): {self.sampling_methods}" + ) + raise InvalidIcclimArgumentError(msg) + if self.check_vars is not None: + self.check_vars(climate_vars, self) + def count_occurrences( climate_vars: list[ClimateVariable], @@ -324,22 +351,23 @@ def count_occurrences( logical_link: LogicalLink, date_event: bool, to_percent: bool, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: if date_event: reducer_op = _count_occurrences_with_date else: reducer_op = partial(DataArray.sum, dim="time") merged_exceedances = _compute_exceedances( - climate_vars, resample_freq.pandas_freq, logical_link + climate_vars, + resample_freq.pandas_freq, + logical_link, ) result = reducer_op(merged_exceedances.resample(time=resample_freq.pandas_freq)) if to_percent: result = _to_percent(result, resample_freq) result.attrs[UNITS_KEY] = "%" return result - else: - return to_agg_units(result, climate_vars[0].studied_data, "count") + return to_agg_units(result, climate_vars[0].studied_data, "count") def max_consecutive_occurrence( @@ -348,10 +376,12 @@ def max_consecutive_occurrence( logical_link: LogicalLink, date_event: bool, source_freq_delta: timedelta, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: merged_exceedances = _compute_exceedances( - climate_vars, resample_freq.pandas_freq, logical_link + climate_vars, + resample_freq.pandas_freq, + logical_link, ) rle = run_length.rle(merged_exceedances, dim="time", index="first") resampled = rle.resample(time=resample_freq.pandas_freq) @@ -367,10 +397,12 @@ def sum_of_spell_lengths( resample_freq: Frequency, logical_link: LogicalLink, min_spell_length: int, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: merged_exceedances = _compute_exceedances( - climate_vars, resample_freq.pandas_freq, logical_link + climate_vars, + resample_freq.pandas_freq, + logical_link, ) rle = run_length.rle(merged_exceedances, dim="time", index="first") cropped_rle = rle.where(rle >= min_spell_length, other=0) @@ -381,11 +413,12 @@ def sum_of_spell_lengths( def excess( climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: study, threshold = get_single_var(climate_vars) if threshold.operator is not OperatorRegistry.REACH: - raise InvalidIcclimArgumentError("") + msg = "" + raise InvalidIcclimArgumentError(msg) excesses = threshold.compute(study, override_op=lambda da, th: da - th) res = ( (excesses).clip(min=0).resample(time=resample_freq.pandas_freq).sum(dim="time") @@ -397,7 +430,7 @@ def excess( def deficit( climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: study, threshold = get_single_var(climate_vars) deficit = threshold.compute(study, override_op=lambda da, th: th - da) @@ -410,7 +443,7 @@ def fraction_of_total( climate_vars: list[ClimateVariable], resample_freq: Frequency, to_percent: bool, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: study, threshold = get_single_var(climate_vars) if threshold.threshold_min_value is not None: @@ -418,8 +451,8 @@ def fraction_of_total( min_val = convert_units_to(min_val, study, context="hydro") total = ( study.where(threshold.operator(study, min_val)) - # study.where(threshold.operator(study, threshold.threshold_min_value.m)) - .resample(time=resample_freq.pandas_freq).sum(dim="time") + .resample(time=resample_freq.pandas_freq) + .sum(dim="time") ) else: total = study.resample(time=resample_freq.pandas_freq).sum(dim="time") @@ -447,7 +480,7 @@ def maximum( climate_vars: list[ClimateVariable], resample_freq: Frequency, date_event: bool, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: return _run_simple_reducer( climate_vars=climate_vars, @@ -461,7 +494,7 @@ def minimum( climate_vars: list[ClimateVariable], resample_freq: Frequency, date_event: bool, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: return _run_simple_reducer( climate_vars=climate_vars, @@ -474,7 +507,7 @@ def minimum( def average( climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: return _run_simple_reducer( climate_vars=climate_vars, @@ -484,10 +517,10 @@ def average( ) -def sum( +def sum( # noqa: A001 climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: return _run_simple_reducer( climate_vars=climate_vars, @@ -501,10 +534,13 @@ def sum( def standard_deviation( climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: return _run_simple_reducer( - climate_vars, resample_freq, DataArrayResample.std, date_event=False + climate_vars, + resample_freq, + DataArrayResample.std, + date_event=False, ) @@ -514,7 +550,7 @@ def max_of_rolling_sum( rolling_window_width: int, date_event: bool, source_freq_delta: timedelta, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ): return _run_rolling_reducer( climate_vars=climate_vars, @@ -533,7 +569,7 @@ def min_of_rolling_sum( rolling_window_width: int, date_event: bool, source_freq_delta: timedelta, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ): return _run_rolling_reducer( climate_vars=climate_vars, @@ -552,7 +588,7 @@ def min_of_rolling_average( rolling_window_width: int, date_event: bool, source_freq_delta: timedelta, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ): return _run_rolling_reducer( climate_vars=climate_vars, @@ -571,7 +607,7 @@ def max_of_rolling_average( rolling_window_width: int, date_event: bool, source_freq_delta: timedelta, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ): return _run_rolling_reducer( climate_vars=climate_vars, @@ -587,7 +623,7 @@ def max_of_rolling_average( def mean_of_difference( climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ): study, ref = get_couple_of_var(climate_vars, "mean_of_difference") mean_of_diff = (study - ref).resample(time=resample_freq.pandas_freq).mean() @@ -598,7 +634,7 @@ def mean_of_difference( def difference_of_extremes( climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ): study, ref = get_couple_of_var(climate_vars, "difference_of_extremes") max_study = study.resample(time=resample_freq.pandas_freq).max() @@ -611,7 +647,7 @@ def difference_of_extremes( def mean_of_absolute_one_time_step_difference( climate_vars: list[ClimateVariable], resample_freq: Frequency, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ) -> DataArray: """ Generification of ECAD's vDTR index. @@ -631,7 +667,8 @@ def mean_of_absolute_one_time_step_difference( mean_of_absolute_one_time_step_difference as a xarray.DataArray """ study, ref = get_couple_of_var( - climate_vars, "mean_of_absolute_one_time_step_difference" + climate_vars, + "mean_of_absolute_one_time_step_difference", ) one_time_step_diff = (study - ref).diff(dim="time") res = abs(one_time_step_diff).resample(time=resample_freq.pandas_freq).mean() @@ -645,15 +682,16 @@ def difference_of_means( resample_freq: Frequency, sampling_method: str, is_compared_to_reference: bool, - **kwargs, # noqa + **kwargs, # noqa: ARG001 ): if is_compared_to_reference and sampling_method == RESAMPLE_METHOD: - raise InvalidIcclimArgumentError( + msg = ( "It does not make sense to resample the reference variable if it is" " already a subsample of the studied variable. Try setting" f" `sampling_method='{GROUP_BY_REF_AND_RESAMPLE_STUDY_METHOD}'`" f" instead." ) + raise InvalidIcclimArgumentError(msg) study, ref = get_couple_of_var(climate_vars, "difference_of_means") if sampling_method == GROUP_BY_METHOD: if resample_freq.group_by_key == RUN_INDEXER: @@ -676,10 +714,14 @@ def difference_of_means( mean_ref = ref.mean(dim="time") else: return _diff_of_means_of_resampled_x_by_groupedby_y( - resample_freq, to_percent, study, ref + resample_freq, + to_percent, + study, + ref, ) else: - raise NotImplementedError(f"Unknown sampling_method: '{sampling_method}'.") + msg = f"Unknown sampling_method: '{sampling_method}'." + raise NotImplementedError(msg) diff_of_means = mean_study - mean_ref if to_percent: diff_of_means = diff_of_means / mean_ref * 100 @@ -690,24 +732,28 @@ def difference_of_means( def _diff_of_means_of_resampled_x_by_groupedby_y( - resample_freq: Frequency, to_percent: bool, study: DataArray, ref: DataArray + resample_freq: Frequency, + to_percent: bool, + study: DataArray, + ref: DataArray, ) -> DataArray: mean_ref = ref.groupby(resample_freq.group_by_key).mean() acc = [] if resample_freq == FrequencyRegistry.MONTH: key = "month" - dt_selector = lambda x: x.time.dt.month # noqa lamdab assigned + dt_selector = lambda x: x.time.dt.month # noqa: E731 elif resample_freq == FrequencyRegistry.DAY: key = "dayofyear" - dt_selector = lambda x: x.time.dt.dayofyear # noqa lamdab assigned + dt_selector = lambda x: x.time.dt.dayofyear # noqa: E731 else: - raise NotImplementedError( + msg = ( f"Can't use {GROUP_BY_REF_AND_RESAMPLE_STUDY_METHOD}" f" with the frequency {resample_freq.long_name}." ) + raise NotImplementedError(msg) for label, sample in study.resample(time=resample_freq.pandas_freq): sample_mean = sample.mean(dim="time") - ref_group_mean = mean_ref.sel({key: dt_selector(sample).values[0]}) + ref_group_mean = mean_ref.sel({key: dt_selector(sample).to_numpy()[0]}) sample_diff_of_means = sample_mean - ref_group_mean if to_percent: sample_diff_of_means = sample_diff_of_means / ref_group_mean * 100 @@ -724,22 +770,23 @@ def _diff_of_means_of_resampled_x_by_groupedby_y( def _check_single_var(climate_vars: list[ClimateVariable], indicator: GenericIndicator): if len(climate_vars) > 1: - raise InvalidIcclimArgumentError( - f"{indicator.name} can only be computed on a single variable." - ) + msg = f"{indicator.name} can only be computed on a single variable." + raise InvalidIcclimArgumentError(msg) def _check_couple_of_vars( - climate_vars: list[ClimateVariable], indicator: GenericIndicator + climate_vars: list[ClimateVariable], + indicator: GenericIndicator, ): if len(climate_vars) != 2: - raise InvalidIcclimArgumentError( + msg = ( f"{indicator.name} can only be computed on two variables sharing the same" f" unit (e.g. 2 temperatures). You can either provide a secondary variable" f" with `in_files` or `var_name`, or you can let icclim compute this" f" second variable as a subset of the first one using" f" `base_period_time_range`." ) + raise InvalidIcclimArgumentError(msg) class GenericIndicatorRegistry(Registry[GenericIndicator]): @@ -892,8 +939,8 @@ def __init__(self): def _compute_exceedance( study: DataArray, threshold: Threshold, - freq: str, # noqa used by @percentile_bootstrap (don't rename, it breaks bootstrap) - bootstrap: bool, # noqa used by @percentile_bootstrap + freq: str, # used by @percentile_bootstrap (don't rename, it breaks bootstrap) + bootstrap: bool, # used by @percentile_bootstrap ) -> DataArray: exceedances = threshold.compute(study, freq=freq, bootstrap=bootstrap) if bootstrap: @@ -904,17 +951,18 @@ def _compute_exceedance( def get_couple_of_var( - climate_vars: list[ClimateVariable], indicator: str + climate_vars: list[ClimateVariable], + indicator: str, ) -> tuple[DataArray, DataArray]: if len(climate_vars) != 2: - raise InvalidIcclimArgumentError( + msg = ( f"{indicator} needs two variables **or** one variable and a " f"`base_period_time_range` period to extract a reference variable." ) + raise InvalidIcclimArgumentError(msg) if climate_vars[0].threshold or climate_vars[1].threshold: - raise InvalidIcclimArgumentError( - f"{indicator} cannot be computed with thresholds." - ) + msg = f"{indicator} cannot be computed with thresholds." + raise InvalidIcclimArgumentError(msg) study = climate_vars[0].studied_data ref = climate_vars[1].studied_data study = convert_units_to(study, ref, context="hydro") @@ -948,8 +996,7 @@ def _run_rolling_reducer( window=rolling_window_width, source_delta=source_freq_delta, ) - else: - return resampled_op(study, dim="time") # type:ignore + return resampled_op(study, dim="time") def _run_simple_reducer( @@ -970,22 +1017,23 @@ def _run_simple_reducer( filtered_study = study.where(exceedance) else: filtered_study = study - if must_convert_rate: - if _is_rate(filtered_study): - filtered_study = rate2amount(filtered_study) + if must_convert_rate and _is_rate(filtered_study): + filtered_study = rate2amount(filtered_study) if date_event: return _reduce_with_date_event( resampled=filtered_study.resample(time=resample_freq.pandas_freq), reducer=reducer_op, ) - else: - return reducer_op( - filtered_study.resample(time=resample_freq.pandas_freq), dim="time" - ) + return reducer_op( + filtered_study.resample(time=resample_freq.pandas_freq), + dim="time", + ) def _compute_exceedances( - climate_vars: list[ClimateVariable], resample_freq: str, logical_link: LogicalLink + climate_vars: list[ClimateVariable], + resample_freq: str, + logical_link: LogicalLink, ) -> DataArray: exceedances = [ _compute_exceedance( @@ -993,7 +1041,8 @@ def _compute_exceedances( threshold=climate_var.threshold, freq=resample_freq, bootstrap=_must_run_bootstrap( - climate_var.studied_data, climate_var.threshold + climate_var.studied_data, + climate_var.threshold, ), ).squeeze() for climate_var in climate_vars @@ -1009,16 +1058,16 @@ def get_single_var( climate_vars[0].studied_data, climate_vars[0].threshold, ) - else: - return climate_vars[0].studied_data, None + return climate_vars[0].studied_data, None def _must_run_bootstrap(da: DataArray, threshold: Threshold | None) -> bool: """Avoid bootstrapping if there is one single year overlapping or no year overlapping or all year overlapping. """ - # TODO: Don't run bootstrap when not on extreme percentile + # TODO @bzah: Don't run bootstrap when not on extreme percentile # (run only below 20? 10? and above 80? 90?) + # https://github.com/cerfacs-globc/icclim/issues/289 if ( threshold is None or not isinstance(threshold, PercentileThreshold) @@ -1031,7 +1080,7 @@ def _must_run_bootstrap(da: DataArray, threshold: Threshold | None) -> bool: reference = threshold.value study_years = np.unique(da.indexes.get("time").year) overlapping_years = np.unique( - da.sel(time=_get_ref_period_slice(reference)).indexes.get("time").year + da.sel(time=_get_ref_period_slice(reference)).indexes.get("time").year, ) return 1 < len(overlapping_years) < len(study_years) @@ -1040,14 +1089,14 @@ def _get_ref_period_slice(da: DataArray) -> slice: if (bds := da.attrs.get("climatology_bounds", None)) is not None: return slice(*bds) time_length = len(da.time) - return slice(*da.time[0 :: time_length - 1].dt.strftime("%Y-%m-%d").values) + return slice(*da.time[0 :: time_length - 1].dt.strftime("%Y-%m-%d").to_numpy()) def _same_freq_for_all(climate_vars: list[ClimateVariable]) -> bool: if len(climate_vars) == 1: return True - freqs = list(map(lambda a: xr.infer_freq(a.studied_data.time), climate_vars)) - return all(map(lambda x: x == freqs[0], freqs[1:])) + freqs = [xr.infer_freq(a.studied_data.time) for a in climate_vars] + return all(x == freqs[0] for x in freqs[1:]) def _get_climate_vars_metadata( @@ -1079,9 +1128,8 @@ def _reduce_with_date_event( elif reducer == DataArrayResample.min: group_reducer = DataArray.argmin else: - raise NotImplementedError( - f"Can't compute `date_event` due to unknown reducer:" f" '{reducer}'" - ) + msg = f"Can't compute `date_event` due to unknown reducer: '{reducer}'" + raise NotImplementedError(msg) for label, sample in resampled: reduced_result = sample.isel(time=group_reducer(sample, dim="time")) if window is not None: @@ -1105,13 +1153,13 @@ def _reduce_with_date_event( def _count_occurrences_with_date(resampled: DataArrayResample): acc: list[DataArray] = [] - for label, sample in resampled: - # Fixme probably not safe to compute on huge dataset, - # it should be fixed with + for label, _sample in resampled: + # TODO @bzah: probably not safe to compute on huge dataset, + # it should be fixed with # https://github.com/pydata/xarray/issues/2511 - sample = sample.compute() + sample = _sample.compute() first = sample.isel(time=sample.argmax("time")).time - reversed_time = sample.reindex(time=list(reversed(sample.time.values))) + reversed_time = sample.reindex(time=list(reversed(sample.time.to_numpy()))) last = reversed_time.isel(time=reversed_time.argmax("time")).time dated_occurrences = _add_date_coords( original_sample=sample, @@ -1125,18 +1173,19 @@ def _count_occurrences_with_date(resampled: DataArrayResample): def _consecutive_occurrences_with_dates( - resampled: DataArrayResample, source_freq_delta: timedelta + resampled: DataArrayResample, + source_freq_delta: timedelta, ): acc = [] - for label, sample in resampled: - sample = sample.where(~sample.isnull(), 0) + for label, _sample in resampled: + sample = _sample.where(~_sample.isnull(), 0) time_index_of_max_rle = sample.argmax(dim="time") - # fixme: `.compute` is needed until xarray merges this pr: - # https://github.com/pydata/xarray/pull/5873 + # TODO @bzah: `.compute` is needed until xarray merges this pr: + # https://github.com/pydata/xarray/pull/5873 time_index_of_max_rle = time_index_of_max_rle.compute() dated_longest_run = sample[{"time": time_index_of_max_rle}] start_time = sample.isel( - time=time_index_of_max_rle.where(time_index_of_max_rle > 0, 0) + time=time_index_of_max_rle.where(time_index_of_max_rle > 0, 0), ).time end_time = start_time + (dated_longest_run * source_freq_delta) dated_longest_run = _add_date_coords( @@ -1147,8 +1196,7 @@ def _consecutive_occurrences_with_dates( label=label, ) acc.append(dated_longest_run) - result = xr.concat(acc, "time") - return result + return xr.concat(acc, "time") def _add_date_coords( @@ -1178,11 +1226,11 @@ def _is_amount_unit(unit: str) -> bool: def _is_a_diff_indicator(indicator: Indicator) -> bool: - return ( - indicator == GenericIndicatorRegistry.DifferenceOfExtremes - or indicator == GenericIndicatorRegistry.MeanOfDifference - or indicator == GenericIndicatorRegistry.MeanOfAbsoluteOneTimeStepDifference - or indicator == GenericIndicatorRegistry.DifferenceOfMeans + return indicator in ( + GenericIndicatorRegistry.DifferenceOfExtremes, + GenericIndicatorRegistry.MeanOfDifference, + GenericIndicatorRegistry.MeanOfAbsoluteOneTimeStepDifference, + GenericIndicatorRegistry.DifferenceOfMeans, ) @@ -1192,7 +1240,8 @@ def _convert_rates_to_amounts(climate_vars: list[ClimateVariable], output_unit: if current_unit is not None and not _is_amount_unit(current_unit): with xc_units.context("hydro"): climate_var.studied_data = rate2amount( - climate_var.studied_data, out_units=output_unit + climate_var.studied_data, + out_units=output_unit, ) return climate_vars @@ -1225,10 +1274,12 @@ def _to_percent(da: DataArray, sampling_freq: Frequency) -> DataArray: elif sampling_freq == FrequencyRegistry.SON: da = da / 91 else: - # TODO improve this for custom resampling + # TODO @bzah: improve this for custom resampling + # https://github.com/cerfacs-globc/icclim/issues/289 warn( "For now, '%' unit can only be used when `slice_mode` is one of: " - "{MONTH, YEAR, AMJJAS, ONDJFM, DJF, MAM, JJA, SON}." + "{MONTH, YEAR, AMJJAS, ONDJFM, DJF, MAM, JJA, SON}.", + stacklevel=2, ) return da da.attrs[UNITS_KEY] = PART_OF_A_WHOLE_UNIT @@ -1239,8 +1290,7 @@ def _is_leap_year(da: DataArray) -> np.ndarray: time_index = da.indexes.get("time") if isinstance(time_index, xr.CFTimeIndex): return CfCalendarRegistry.lookup(time_index.calendar).is_leap(da.time.dt.year) - else: - return da.time.dt.is_leap_year + return da.time.dt.is_leap_year def _check_cf(climate_vars: list[ClimateVariable]): @@ -1255,11 +1305,9 @@ def _check_cf(climate_vars: list[ClimateVariable]): `xclim.core.options.cfcheck`. """ for da in climate_vars: - try: - cfcheck_from_name(str(da.name), da) - except KeyError: + with contextlib.suppress(KeyError): # Silently ignore unknown variables. - pass + cfcheck_from_name(str(da.name), da) def _check_data(climate_vars: list[ClimateVariable], src_freq: str): diff --git a/icclim/generic_indices/generic_templates.py b/src/icclim/generic_indices/generic_templates.py similarity index 100% rename from icclim/generic_indices/generic_templates.py rename to src/icclim/generic_indices/generic_templates.py diff --git a/icclim/generic_indices/standard_variable.py b/src/icclim/generic_indices/standard_variable.py similarity index 93% rename from icclim/generic_indices/standard_variable.py rename to src/icclim/generic_indices/standard_variable.py index 5e3e5178..ed3ef69e 100644 --- a/icclim/generic_indices/standard_variable.py +++ b/src/icclim/generic_indices/standard_variable.py @@ -1,7 +1,7 @@ from __future__ import annotations import dataclasses -from typing import Hashable +from collections.abc import Hashable from icclim.models.constants import PART_OF_A_WHOLE_UNIT from icclim.models.registry import Registry @@ -19,11 +19,11 @@ def __hash__(self) -> int: return hash(self.short_name + self.standard_name) def get_metadata(self): - return dict( - standard_name=self.standard_name, - long_name=self.long_name, - short_name=self.short_name, - ) + return { + "standard_name": self.standard_name, + "long_name": self.long_name, + "short_name": self.short_name, + } class StandardVariableRegistry(Registry[StandardVariable]): @@ -243,14 +243,9 @@ class StandardVariableRegistry(Registry[StandardVariable]): aliases=["dd"], default_units="degree", ) - # X = StandardVariable( - # short_name="x", - # standard_name="y", - # long_name="z", - # aliases=["x"], - # default_units="w", - # ) - # todo add tier1 and tier2 aliases from cmip6/cordex https://docs.google.com/spreadsheets/d/1qUauozwXkq7r1g-L4ALMIkCNINIhhCPx/edit?rtpof=true&sd=true#gid=1672965248 # noqa + # TODO @bzah: add tier1 and tier2 aliases from cmip6/cordex + # https://docs.google.com/spreadsheets/d/1qUauozwXkq7r1g-L4ALMIkCNINIhhCPx/edit?rtpof=true&sd=true#gid=1672965248 + # https://github.com/cerfacs-globc/icclim/issues/289 @staticmethod def get_item_aliases(item: StandardVariable) -> list[str]: diff --git a/icclim/generic_indices/threshold.py b/src/icclim/generic_indices/threshold.py similarity index 83% rename from icclim/generic_indices/threshold.py rename to src/icclim/generic_indices/threshold.py index 2fb63992..303d0546 100644 --- a/icclim/generic_indices/threshold.py +++ b/src/icclim/generic_indices/threshold.py @@ -2,12 +2,10 @@ import abc import re -from datetime import datetime -from typing import Any, Callable, Sequence, TypedDict, Union +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Callable, TypedDict, Union -import jinja2 import numpy as np -import pint import xarray as xr from xarray import DataArray, Dataset from xclim.core.bootstrapping import percentile_bootstrap @@ -29,7 +27,6 @@ PERIOD_PERCENTILE_UNIT, UNITS_KEY, ) -from icclim.models.frequency import Frequency from icclim.models.logical_link import LogicalLink, LogicalLinkRegistry from icclim.models.operator import Operator, OperatorRegistry from icclim.models.quantile_interpolation import ( @@ -48,8 +45,22 @@ ) from icclim.utils import is_number_sequence +if TYPE_CHECKING: + from datetime import datetime + + import jinja2 + import pint + + from icclim.models.frequency import Frequency + ThresholdValueType = Union[ - str, float, int, Dataset, DataArray, Sequence[Union[float, int, str]], None + str, + float, + int, + Dataset, + DataArray, + Sequence[Union[float, int, str]], + None, ] @@ -71,7 +82,8 @@ class ThresholdBuilderInput(TypedDict, total=False): reference_period: Sequence[datetime | str] | None # bounded conf: thresholds: tuple[ - ThresholdBuilderInput | Threshold, ThresholdBuilderInput | Threshold + ThresholdBuilderInput | Threshold, + ThresholdBuilderInput | Threshold, ] | None logical_link: LogicalLink @@ -91,7 +103,6 @@ def build_threshold( Parameters ---------- - query: str | None = None string query describing a threshold. It must include: an operator, a threshold value and optionally a unit @@ -191,12 +202,12 @@ def build_threshold( ) if _must_build_per_threshold(input_thresh): return PercentileThreshold(**input_thresh) - elif _must_build_basic_threshold(input_thresh): + if _must_build_basic_threshold(input_thresh): return BasicThreshold(**input_thresh) - elif _must_build_bounded_threshold(input_thresh): + if _must_build_bounded_threshold(input_thresh): return BoundedThreshold(**input_thresh) - else: - raise NotImplementedError(f"Threshold cannot be built from a {type(value)}") + msg = f"Threshold cannot be built from a {type(value)}" + raise NotImplementedError(msg) class Threshold(metaclass=abc.ABCMeta): @@ -213,7 +224,11 @@ class Threshold(metaclass=abc.ABCMeta): @abc.abstractmethod def format_metadata( - self, *, jinja_scope: dict[str, Any], jinja_env: jinja2.Environment, **kwargs + self, + *, + jinja_scope: dict[str, Any], + jinja_env: jinja2.Environment, + **kwargs, ) -> ThresholdMetadata: """Get a dictionary of standardized threshold metadata.""" ... @@ -286,8 +301,7 @@ class BoundedThreshold(Threshold): def unit(self) -> str | None: if self.left_threshold.unit == self.right_threshold.unit: return self.left_threshold.unit - else: - return None + return None @unit.setter def unit(self, unit): @@ -299,20 +313,22 @@ def __init__( thresholds: Sequence[Threshold | str | ThresholdBuilderInput], logical_link: LogicalLink, initial_query: str | None, - **kwargs, # noqa + **kwargs, # noqa: ARG002 ): if len(thresholds) != 2: - raise InvalidIcclimArgumentError( + msg = ( f"BoundedThreshold can only be built on 2 thresholds, {len(thresholds)}" f" were found." ) + raise InvalidIcclimArgumentError(msg) self.left_threshold = self._build_thresh(thresholds[0]) self.right_threshold = self._build_thresh(thresholds[1]) if self.left_threshold == self.right_threshold: - raise InvalidIcclimArgumentError( + msg = ( f"BoundedThreshold must be built on 2 **different** thresholds, here" f" both were {self.left_threshold.initial_query}" ) + raise InvalidIcclimArgumentError(msg) self.logical_link = logical_link self.initial_query = initial_query @@ -323,24 +339,36 @@ def compute( **kwargs, ) -> DataArray: left_res = self.left_threshold.compute( - comparison_data, override_op=override_op, **kwargs + comparison_data, + override_op=override_op, + **kwargs, ) right_res = self.right_threshold.compute( - comparison_data, override_op=override_op, **kwargs + comparison_data, + override_op=override_op, + **kwargs, ) return self.logical_link.compute([left_res, right_res]) def format_metadata( - self, *, jinja_scope: dict[str, Any], jinja_env: jinja2.Environment, **kwargs + self, + *, + jinja_scope: dict[str, Any], + jinja_env: jinja2.Environment, + **kwargs, ) -> ThresholdMetadata: templates = self._get_metadata_templates() conf = { "left_threshold": self.left_threshold.format_metadata( - jinja_scope=jinja_scope, jinja_env=jinja_env, **kwargs + jinja_scope=jinja_scope, + jinja_env=jinja_env, + **kwargs, ), "logical_link": self.logical_link, "right_threshold": self.right_threshold.format_metadata( - jinja_scope=jinja_scope, jinja_env=jinja_env, **kwargs + jinja_scope=jinja_scope, + jinja_env=jinja_env, + **kwargs, ), } conf.update(jinja_scope) @@ -368,26 +396,20 @@ def __eq__(self, other: BoundedThreshold) -> bool: return ( isinstance(other, BoundedThreshold) and self.initial_query == other.initial_query - and ( - self.left_threshold == other.left_threshold - or self.left_threshold == other.right_threshold - ) - and ( - self.right_threshold == other.left_threshold - or self.right_threshold == other.right_threshold - ) + and (self.left_threshold in (other.left_threshold, other.right_threshold)) + and (self.right_threshold in (other.left_threshold, other.right_threshold)) and self.logical_link == other.logical_link ) def _build_thresh( - self, thresh_input: Threshold | str | ThresholdBuilderInput + self, + thresh_input: Threshold | str | ThresholdBuilderInput, ) -> Threshold: if isinstance(thresh_input, Threshold): return thresh_input - elif isinstance(thresh_input, str): + if isinstance(thresh_input, str): return build_threshold(thresh_input) - else: - return build_threshold(**thresh_input) + return build_threshold(**thresh_input) def _get_metadata_templates(self) -> ThresholdMetadata: return EN_THRESHOLD_TEMPLATE["bounded_threshold"] @@ -441,7 +463,9 @@ def unit(self, unit: str | xr.DataArray | pint.Quantity | pint.Unit): if self.is_ready: if self.value.attrs.get(UNITS_KEY, None) is not None and unit is not None: self._prepared_value = convert_units_to( - self._prepared_value, unit, context="hydro" + self._prepared_value, + unit, + context="hydro", ) self.value.attrs[UNITS_KEY] = unit @@ -449,17 +473,17 @@ def unit(self, unit: str | xr.DataArray | pint.Quantity | pint.Unit): def value(self) -> PercentileDataArray: if self.is_ready: return self._prepared_value - else: - raise RuntimeError( - "Property `value` is not ready. For PercentileDataArray," - " you must call `.prepare` first and fill `studied_data`" - " parameter in order to prepare `value`." - ) + msg = ( + "Property `value` is not ready. For PercentileDataArray," + " you must call `.prepare` first and fill `studied_data`" + " parameter in order to prepare `value`." + ) + raise RuntimeError(msg) def __init__( self, operator: str | Operator, - value: DataArray | float | int | Sequence[float], + value: DataArray | float | Sequence[float], unit: str | None = None, doy_window_width: int = DEFAULT_DOY_WINDOW, only_leap_years: bool = False, @@ -469,7 +493,7 @@ def __init__( threshold_min_value: pint.Quantity | None = None, initial_query: str | None = None, threshold_var_name: str | None = None, - **kwargs, # noqa + **kwargs, # noqa: ARG002 ): if is_dataset_path(value) or isinstance(value, Dataset): value, is_doy_per_threshold = _build_per_thresh_from_dataset( @@ -526,13 +550,12 @@ def prepare(self, studied_data: DataArray) -> None: percentile_min_value=self.threshold_min_value, ) else: - raise NotImplementedError( - f"Unknown percentile unit" f" '{self._initial_unit}'." - ) + msg = f"Unknown percentile unit '{self._initial_unit}'." + raise NotImplementedError(msg) self._prepared_value = prepared_data.chunk("auto") self.is_ready = True - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: return ( isinstance(other, PercentileThreshold) and self.initial_query == other.initial_query @@ -554,7 +577,7 @@ def format_metadata( jinja_env: jinja2.Environment, src_freq: Frequency, must_run_bootstrap: bool = False, - **kwargs, + **kwargs, # noqa: ARG002 ) -> ThresholdMetadata: per_coord = self.value.coords["percentiles"] templates = self._get_metadata_templates(per_coord) @@ -581,10 +604,7 @@ def compute( override_op: Callable[[DataArray, DataArray], DataArray] | None = None, **kwargs, ) -> DataArray: - if override_op is not None: - op = override_op - else: - op = self.operator + op = override_op if override_op is not None else self.operator if self.is_ready: return self._per_compute( comparison_data, @@ -594,24 +614,21 @@ def compute( kwargs.get("freq", None), kwargs.get("bootstrap", False), ) - else: - raise RuntimeError( - "This PercentileThreshold is not ready. You must first call `.prepare`" - " with a `studied_data` parameter in order to prepare the threshold" - " for computation." - ) + msg = ( + "This PercentileThreshold is not ready. You must first call `.prepare`" + " with a `studied_data` parameter in order to prepare the threshold" + " for computation." + ) + raise RuntimeError(msg) def _get_metadata_templates(self, per_coord: DataArray) -> ThresholdMetadata: if self.is_doy_per_threshold: if per_coord.size == 1: return EN_THRESHOLD_TEMPLATE["single_doy_percentile"] - else: - return EN_THRESHOLD_TEMPLATE["multiple_doy_percentiles"] - else: - if per_coord.size == 1: - return EN_THRESHOLD_TEMPLATE["single_period_percentile"] - else: - return EN_THRESHOLD_TEMPLATE["multiple_period_percentiles"] + return EN_THRESHOLD_TEMPLATE["multiple_doy_percentiles"] + if per_coord.size == 1: + return EN_THRESHOLD_TEMPLATE["single_period_percentile"] + return EN_THRESHOLD_TEMPLATE["multiple_period_percentiles"] @percentile_bootstrap def _per_compute( @@ -620,8 +637,8 @@ def _per_compute( per: xr.DataArray, op: Callable[[DataArray, DataArray], DataArray], is_doy_per_threshold: bool, - freq: str, # noqa used by @percentile_bootstrap - bootstrap: bool, # noqa used by @percentile_bootstrap + freq: str, # noqa: ARG002 used by @percentile_bootstrap + bootstrap: bool, # noqa: ARG002 used by @percentile_bootstrap ) -> DataArray: if self.threshold_min_value is not None: # there is only a threshold_min_value when we are computing > or >= @@ -664,16 +681,15 @@ def __init__( initial_query: str | None = None, threshold_min_value: pint.Quantity | None = None, threshold_var_name: str | None = None, - **kwargs, # noqa + **kwargs, # noqa: ARG002 ): if ( is_number_sequence(value) or isinstance(value, (float, int)) ) and threshold_min_value is not None: - raise InvalidIcclimArgumentError( - "Cannot use threshold_min_value with scalars" - ) + msg = "Cannot use threshold_min_value with scalars" + raise InvalidIcclimArgumentError(msg) if is_dataset_path(value) or isinstance(value, Dataset): - # e.g. build_threshold(">", "thresh*.nc" , "degC") + # e.g. build_threshold(">", "thresh*.nc" , "degC") noqa: ERA001 thresh_da = _get_dataarray_from_dataset(threshold_var_name, value) built_value = _apply_min_value(thresh_da, threshold_min_value) if unit is None: @@ -691,12 +707,15 @@ def __init__( coords={"threshold": value}, ) elif isinstance(value, (float, int)): - # e.g. build_threshold(">", [2,3,4], "degC") + # e.g. build_threshold(">", [2,3,4], "degC") noqa: ERA001 built_value = DataArray( - name="threshold", data=value, attrs={UNITS_KEY: unit} + name="threshold", + data=value, + attrs={UNITS_KEY: unit}, ) else: - raise NotImplementedError(f"Cannot build threshold from a {type(value)}.") + msg = f"Cannot build threshold from a {type(value)}." + raise NotImplementedError(msg) if unit is not None: built_value = convert_units_to(built_value, unit, context="hydro") self.operator = operator @@ -726,14 +745,13 @@ def __eq__(self, other): def _get_metadata_templates(self) -> ThresholdMetadata: if self.value.size == 1: return EN_THRESHOLD_TEMPLATE["single_value"] - else: - return EN_THRESHOLD_TEMPLATE["multiple_values"] + return EN_THRESHOLD_TEMPLATE["multiple_values"] def format_metadata( self, jinja_scope: dict[str, Any], jinja_env: jinja2.Environment, - **kwargs, + **kwargs, # noqa: ARG002 ) -> ThresholdMetadata: templates = self._get_metadata_templates() conf = { @@ -745,10 +763,12 @@ def format_metadata( } if self.value.size > 1: conf["min_value"] = np.format_float_positional( - self.value.min().values[()], 3 + self.value.min().values[()], + 3, ) conf["max_value"] = np.format_float_positional( - self.value.max().values[()], 3 + self.value.max().values[()], + 3, ) conf.update(jinja_scope) return { @@ -760,7 +780,7 @@ def compute( self, comparison_data: xr.DataArray, override_op: Callable[[DataArray, DataArray], DataArray] | None = None, - **kwargs, + **kwargs, # noqa: ARG002 ) -> DataArray: if override_op is not None: return override_op(comparison_data, self.value) @@ -787,24 +807,23 @@ def _build_period_per( input_core_dims=[["time"]], output_core_dims=[["percentiles"]], keep_attrs=True, - kwargs=dict( - percentiles=per_val, - alpha=interpolation.alpha, - beta=interpolation.beta, - copy=True, - ), + kwargs={ + "percentiles": per_val, + "alpha": interpolation.alpha, + "beta": interpolation.beta, + "copy": True, + }, dask="parallelized", output_dtypes=[reference.dtype], - dask_gufunc_kwargs=dict(output_sizes={"percentiles": 1}, allow_rechunk=True), + dask_gufunc_kwargs={"output_sizes": {"percentiles": 1}, "allow_rechunk": True}, ) computed_per = computed_per.assign_coords( - percentiles=xr.DataArray(per_val, dims=("percentiles",)) + percentiles=xr.DataArray(per_val, dims=("percentiles",)), ) - res = PercentileDataArray.from_da( + return PercentileDataArray.from_da( source=computed_per, climatology_bounds=build_climatology_bounds(reference), ) - return res def _build_doy_per( @@ -822,29 +841,26 @@ def _build_doy_per( only_leap_years, percentile_min_value, ) - res = percentile_doy( + return percentile_doy( arr=reference, window=doy_window_width, per=per_val, alpha=interpolation.alpha, beta=interpolation.beta, ).compute() # "optimization" (diminish dask scheduler workload) - return res def _read_string_threshold(query: str) -> tuple[str, str, float]: value = re.findall(r"-?\d+\.?\d*", query) if len(value) == 0: - raise InvalidIcclimArgumentError(f"Cannot build threshold from '{query}'") + msg = f"Cannot build threshold from '{query}'" + raise InvalidIcclimArgumentError(msg) value = value[0] value_index = query.find(value) operator = query[0:value_index].strip() if operator == "": operator = None - if query.endswith(value): - unit = None - else: - unit = query[value_index + len(value) :].strip() + unit = None if query.endswith(value) else query[value_index + len(value) :].strip() return operator, unit, float(value) @@ -854,30 +870,26 @@ def _build_min_value( ) -> pint.Quantity | None: if threshold_min_value is None: return None - elif isinstance(threshold_min_value, xc_units.Quantity): + if isinstance(threshold_min_value, xc_units.Quantity): return threshold_min_value - elif isinstance(threshold_min_value, (float, int)): - if ( - default_unit == PERIOD_PERCENTILE_UNIT - or default_unit == DOY_PERCENTILE_UNIT - ): + if isinstance(threshold_min_value, (float, int)): + if default_unit in (PERIOD_PERCENTILE_UNIT, DOY_PERCENTILE_UNIT): unit = None else: unit = default_unit return xc_units.Quantity(value=threshold_min_value, units=unit) - elif isinstance(threshold_min_value, str): + if isinstance(threshold_min_value, str): operator, unit, value = _read_string_threshold(threshold_min_value) if operator is not None and operator != "" and operator != ">=": - raise InvalidIcclimArgumentError( + msg = ( f"cannot compute threshold_min_value with" f" {operator}. You don't need to fill an" f" operator for this parameter." ) + raise InvalidIcclimArgumentError(msg) return xc_units.Quantity(value=value, units=unit) - else: - raise NotImplementedError( - f"Unknown type '{type(threshold_min_value)}' for `threshold_min_value`." - ) + msg = f"Unknown type '{type(threshold_min_value)}' for `threshold_min_value`." + raise NotImplementedError(msg) def _read_input( @@ -893,11 +905,10 @@ def _read_input( if _must_read_query(query, operator, value, unit): if _is_bounded_threshold_query(query): return _read_bounded_threshold_query(query) - else: - return _read_threshold_from_query(query, threshold_min_value, kwargs) - elif _must_read_bounded(operator, value, unit, thresholds, logical_link): + return _read_threshold_from_query(query, threshold_min_value, kwargs) + if _must_read_bounded(operator, value, unit, thresholds, logical_link): return _read_bounded_threshold(thresholds, logical_link) - elif _must_read_from_args(operator, value): + if _must_read_from_args(operator, value): if (operator := OperatorRegistry.lookup(operator, no_error=True)) is None: operator = OperatorRegistry.REACH return { @@ -907,12 +918,13 @@ def _read_input( "threshold_min_value": _build_min_value(threshold_min_value, unit), **kwargs, } - else: - raise NotImplementedError("Could not read threshold") + msg = "Could not read threshold" + raise NotImplementedError(msg) def _read_bounded_threshold( - thresholds: tuple[Threshold, Threshold], logical_link: LogicalLink | str + thresholds: tuple[Threshold, Threshold], + logical_link: LogicalLink | str, ) -> ThresholdBuilderInput: acc = [] for t in thresholds: @@ -921,14 +933,14 @@ def _read_bounded_threshold( elif isinstance(t, Threshold): acc.append(t) else: - raise NotImplementedError(f"Unknown type '{type(t)}'") + msg = f"Unknown type '{type(t)}'" + raise NotImplementedError(msg) if len(acc) > 2: - raise NotImplementedError( - "Can't build BoundedThreshold on more than 2 thresholds." - ) + msg = "Can't build BoundedThreshold on more than 2 thresholds." + raise NotImplementedError(msg) if isinstance(logical_link, str): logical_link = LogicalLinkRegistry.lookup(logical_link) - return { # noqa + return { "initial_query": None, "thresholds": tuple(acc), "logical_link": logical_link, @@ -986,7 +998,7 @@ def _must_read_from_args(operator: Operator | str, value: ThresholdValueType) -> def _is_bounded_threshold_query(query: str) -> bool: return any( - [l_l.name.upper() in query.upper() for l_l in LogicalLinkRegistry.values()] + l_l.name.upper() in query.upper() for l_l in LogicalLinkRegistry.values() ) @@ -1001,13 +1013,15 @@ def _read_bounded_threshold_query(query: str) -> ThresholdBuilderInput: split_word = query[index_of_link : index_of_link + len(l_l.name)] break if link is None: - raise InvalidIcclimArgumentError(f"No logical link found in {query}") + msg = f"No logical link found in {query}" + raise InvalidIcclimArgumentError(msg) threshs = query.split(split_word) if len(threshs) != 2: - raise InvalidIcclimArgumentError( + msg = ( "BoundedThreshold can only be built on 2" f" thresholds. We found {len(threshs)} here." ) + raise InvalidIcclimArgumentError(msg) return { "initial_query": query, "thresholds": (_read_input(threshs[0]), _read_input(threshs[1])), @@ -1015,10 +1029,10 @@ def _read_bounded_threshold_query(query: str) -> ThresholdBuilderInput: } -def _must_build_per_threshold(input: ThresholdBuilderInput) -> bool: - value = input.get("value") - unit = input.get("unit", None) - var_name = input.get("threshold_var_name", None) +def _must_build_per_threshold(builder_input: ThresholdBuilderInput) -> bool: + value = builder_input.get("value") + unit = builder_input.get("unit", None) + var_name = builder_input.get("threshold_var_name", None) return _has_per_unit(unit, value) or _is_per_dataset(var_name, value) @@ -1032,25 +1046,21 @@ def _is_per_dataset(threshold_var_name: str, value: str | Dataset | DataArray) - def _has_per_unit(unit: str | None | Sequence[float], value: float) -> bool: return isinstance(value, (float, Sequence)) and ( - unit == DOY_PERCENTILE_UNIT or unit == PERIOD_PERCENTILE_UNIT + unit in (DOY_PERCENTILE_UNIT, PERIOD_PERCENTILE_UNIT) ) -def _must_build_basic_threshold(input: ThresholdBuilderInput) -> bool: - value = input.get("value") - threshold_var_name = input.get("threshold_var_name", None) +def _must_build_basic_threshold(builder_input: ThresholdBuilderInput) -> bool: + value = builder_input.get("value") + threshold_var_name = builder_input.get("threshold_var_name", None) if is_dataset_path(value) or isinstance(value, Dataset): thresh_da = _get_dataarray_from_dataset(threshold_var_name, value) return not PercentileDataArray.is_compatible(thresh_da) - return ( - is_number_sequence(value) - or isinstance(value, (float, int)) - or isinstance(value, DataArray) - ) + return is_number_sequence(value) or isinstance(value, (DataArray, float, int)) -def _must_build_bounded_threshold(input: ThresholdBuilderInput) -> bool: - logical_link = input.get("logical_link") +def _must_build_bounded_threshold(builder_input: ThresholdBuilderInput) -> bool: + logical_link = builder_input.get("logical_link") return logical_link is not None @@ -1061,19 +1071,15 @@ def _apply_min_value(thresh_da: DataArray, min_value: pint.Quantity | None): min_value = min_value.m else: min_value = convert_units_to(str(min_value), thresh_da, context="hydro") - built_value = thresh_da.where(thresh_da > min_value, np.nan) - return built_value - else: - return thresh_da + return thresh_da.where(thresh_da > min_value, np.nan) + return thresh_da def _get_dataarray_from_dataset( - threshold_var_name: str | None, value: Dataset | str + threshold_var_name: str | None, + value: Dataset | str, ) -> DataArray: - if isinstance(value, Dataset): - ds = value - else: - ds = read_dataset(value, standard_var=None) + ds = value if isinstance(value, Dataset) else read_dataset(value, standard_var=None) if threshold_var_name is None: if len(ds.data_vars) == 1: threshold_var_name = get_name_of_first_var(ds) @@ -1082,13 +1088,13 @@ def _get_dataarray_from_dataset( if len(names) == 1: threshold_var_name = names[0] else: - raise InvalidIcclimArgumentError( + msg = ( f"Could not guess the variable to use as a threshold in {ds}." f" Use `threshold_var_name` to specify which variable should be" f" used." ) - thresh_da = ds[threshold_var_name] - return thresh_da + raise InvalidIcclimArgumentError(msg) + return ds[threshold_var_name] def _build_per_thresh_from_dataset( diff --git a/icclim/generic_indices/threshold_templates.py b/src/icclim/generic_indices/threshold_templates.py similarity index 100% rename from icclim/generic_indices/threshold_templates.py rename to src/icclim/generic_indices/threshold_templates.py diff --git a/icclim/icclim_exceptions.py b/src/icclim/icclim_exceptions.py similarity index 85% rename from icclim/icclim_exceptions.py rename to src/icclim/icclim_exceptions.py index 3fc15d71..e303bbf4 100644 --- a/icclim/icclim_exceptions.py +++ b/src/icclim/icclim_exceptions.py @@ -12,7 +12,7 @@ class InvalidIcclimArgumentError(ValueError): The source of the error if any. """ - def __init__(self, msg: str, source_err: Exception = None): + def __init__(self, msg: str, source_err: Exception | None = None): self.msg = msg self.source = source_err diff --git a/icclim/icclim_logger.py b/src/icclim/icclim_logger.py similarity index 100% rename from icclim/icclim_logger.py rename to src/icclim/icclim_logger.py diff --git a/src/icclim/icclim_types.py b/src/icclim/icclim_types.py new file mode 100644 index 00000000..b00ef56b --- /dev/null +++ b/src/icclim/icclim_types.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from collections.abc import Sequence +from typing import Literal, Union + +from xarray import DataArray, Dataset + +InFileBaseType = Union[str, Sequence[str], Dataset, DataArray] +ThresholdedDict = dict[str, Union[dict]] # Dict === InFileDictionary +InFileLike = Union[ThresholdedDict, InFileBaseType, dict[str, InFileBaseType]] + +FrequencyLike = Union[str, list[Union[str, tuple, int]], tuple[str, Union[list, tuple]]] +# MonthsIndexer format: [12,1,2,3] +MonthsIndexer = dict[Literal["month"], Sequence[int]] +# DatesIndexer format: ("01-25", "02-28") +DatesIndexer = dict[Literal["date_bounds"], tuple[str, str]] +Indexer = Union[MonthsIndexer, DatesIndexer] + +SamplingMethodLike = Literal["groupby", "resample", "groupby_ref_and_resample_study"] + +ThresholdValueType = Union[ + str, + float, + int, + Dataset, + DataArray, + Sequence[Union[float, int, str]], + None, +] diff --git a/icclim/main.py b/src/icclim/main.py similarity index 84% rename from icclim/main.py rename to src/icclim/main.py index 1e5f98c0..5bf767bc 100644 --- a/icclim/main.py +++ b/src/icclim/main.py @@ -3,22 +3,22 @@ # Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) """ Main entry point of icclim. + This module expose icclim principal function, notably `index` which is use by the generated API. A convenience function `indices` is also exposed to compute multiple indices at once. """ from __future__ import annotations +import datetime as dt import time -from datetime import datetime +from collections.abc import Sequence from functools import partial, reduce -from typing import Callable, Literal, Sequence +from typing import TYPE_CHECKING, Callable, Literal from warnings import warn import xarray as xr import xclim -from xarray.core.dataarray import DataArray -from xarray.core.dataset import Dataset from icclim.ecad.ecad_indices import EcadIndexRegistry from icclim.ecad.xclim_binding import XCLIM_BINDING @@ -30,21 +30,19 @@ from icclim.generic_indices.threshold import Threshold, build_threshold from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.icclim_logger import IcclimLogger, Verbosity, VerbosityRegistry -from icclim.icclim_types import InFileLike, SamplingMethodLike from icclim.models.climate_variable import ( ClimateVariable, build_climate_vars, read_in_files, ) from icclim.models.constants import ( - ICCLIM_VERSION, PERCENTILE_THRESHOLD_STAMP, RESAMPLE_METHOD, UNITS_KEY, USER_INDEX_PRECIPITATION_STAMP, USER_INDEX_TEMPERATURE_STAMP, ) -from icclim.models.frequency import Frequency, FrequencyLike, FrequencyRegistry +from icclim.models.frequency import Frequency, FrequencyRegistry from icclim.models.index_config import IndexConfig from icclim.models.index_group import IndexGroup, IndexGroupRegistry from icclim.models.logical_link import LogicalLink, LogicalLinkRegistry @@ -54,12 +52,18 @@ QuantileInterpolation, QuantileInterpolationRegistry, ) -from icclim.models.standard_index import StandardIndex -from icclim.models.user_index_dict import UserIndexDict -from icclim.pre_processing.in_file_dictionary import InFileDictionary from icclim.user_indices.calc_operation import CalcOperationRegistry from icclim.utils import read_date +if TYPE_CHECKING: + from xarray.core.dataarray import DataArray + from xarray.core.dataset import Dataset + + from icclim.icclim_types import FrequencyLike, InFileLike, SamplingMethodLike + from icclim.models.standard_index import StandardIndex + from icclim.models.user_index_dict import UserIndexDict + from icclim.pre_processing.in_file_dictionary import InFileDictionary + log: IcclimLogger = IcclimLogger.get_instance(VerbosityRegistry.LOW) HISTORY_CF_KEY = "history" @@ -68,14 +72,15 @@ def indices( - index_group: Literal["all"] | str | IndexGroup | Sequence[str], + index_group: str | IndexGroup | Sequence[str], + *, ignore_error: bool = False, **kwargs, ) -> Dataset: """ Compute multiple indices at the same time. The input dataset(s) must include all the necessary variables. - It can only be used with keyword arguments (kwargs) + It can only be used with keyword arguments (kwargs). .. notes If ``output_file`` is part of kwargs, the result is written in a single netCDF @@ -104,12 +109,12 @@ def indices( """ indices = _get_indices_of_group(index_group) out = None - if "out_file" in kwargs.keys(): + if "out_file" in kwargs: out = kwargs["out_file"] del kwargs["out_file"] acc = [] for i in indices: - log.info(f"Computing index '{i.short_name}'") + log.info("Computing index %s", i.short_name) kwargs["index_name"] = i.short_name if ignore_error: try: @@ -119,8 +124,8 @@ def indices( if "thresholds" in res.coords: res = res.rename({"thresholds": i.short_name + "_thresholds"}) acc.append(res) - except Exception: - warn(f"Could not compute {i.short_name}.") + except Exception: # noqa: BLE001 (catch everything) + warn(f"Could not compute {i.short_name}.", stacklevel=2) else: res = index(**kwargs) if "percentiles" in res.coords: @@ -160,8 +165,8 @@ def _get_indices_of_group( for ecad_index in EcadIndexRegistry.values(): has_var = True for var in ecad_index.input_variables: - is_query_in_aliases = map( - lambda standard_var: standard_var in var.aliases, query + is_query_in_aliases = ( + standard_var in var.aliases for standard_var in query ) has_var &= any(is_query_in_aliases) if has_var: @@ -171,11 +176,12 @@ def _get_indices_of_group( # -- Look for index group (e.g. index_group='HEAT') groups = [IndexGroupRegistry.lookup(i, no_error=True) for i in query] groups = list(filter(lambda x: x is not None, groups)) - indices = map(lambda x: x.get_indices(), groups) + indices = (x.get_indices() for x in groups) indices = reduce(lambda x, y: x + y, indices, []) # flatten list[list] if len(indices) >= len(query): return indices - raise InvalidIcclimArgumentError(f"The index group {query} was not recognized.") + msg = f"The index group {query} was not recognized." + raise InvalidIcclimArgumentError(msg) def indice(*args, **kwargs) -> Dataset: @@ -192,13 +198,13 @@ def index( index_name: str | None = None, # optional when computing user_indices var_name: str | Sequence[str] | None = None, slice_mode: FrequencyLike | Frequency = "year", - time_range: Sequence[datetime | str] | None = None, + time_range: Sequence[dt.datetime | str] | None = None, out_file: str | None = None, threshold: str | Threshold | Sequence[str | Threshold] = None, callback: Callable[[int], None] = log.callback, callback_percentage_start_value: int = 0, callback_percentage_total: int = 100, - base_period_time_range: Sequence[datetime] | Sequence[str] | None = None, + base_period_time_range: Sequence[dt.datetime] | Sequence[str] | None = None, doy_window_width: int = 5, only_leap_years: bool = False, ignore_Feb29th: bool = False, @@ -216,16 +222,15 @@ def index( # deprecated params are kwargs only window_width: int | None = None, save_percentile: bool | None = None, - indice_name: str = None, + indice_name: str | None = None, user_indice: UserIndexDict = None, - transfer_limit_Mbytes: float = None, + transfer_limit_Mbytes: float | None = None, ) -> Dataset: """ Main entry point for icclim to compute climate indices. Parameters ---------- - in_files: str | list[str] | Dataset | DataArray | InputDictionary Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. @@ -235,19 +240,20 @@ def index( For user index, it's the name of the output variable. var_name: str | list[str] | None ``optional`` Target variable name to process corresponding to ``in_files``. - If None (default) on ECA&D index, the variable is guessed based on the climate - index wanted. + If None (default) on ECA&D index, the variable is guessed based on the + climate index wanted. Mandatory for a user index. slice_mode: SliceMode Type of temporal aggregation: The possibles values are ``{"year", "month", "DJF", "MAM", "JJA", "SON", "ONDJFM" or "AMJJAS", ("season", [1,2,3]), ("month", [1,2,3,])}`` - (where season and month lists can be customized) or any valid pandas frequency. + (where season and month lists can be customized) or any valid pandas + frequency. A season can also be defined between two exact dates: ``("season", ("19 july", "14 august"))``. Default is "year". See :ref:`slice_mode` for details. - time_range: list[datetime ] | list[str] | tuple[str, str] | None + time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range: upper and lower bounds for temporal subsetting. If ``None``, whole period of input files will be processed. The dates can either be given as instance of datetime.datetime or as string @@ -273,7 +279,7 @@ def index( ``optional`` Initial value of percentage of the progress bar (default: 0). callback_percentage_total: int ``optional`` Total percentage value (default: 100). - base_period_time_range: list[datetime ] | list[str] | tuple[str, str] | None + base_period_time_range: list[datetime.datetime ] | list[str] | tuple[str, str] | None ``optional`` Temporal range of the reference period. The dates can either be given as instance of datetime.datetime or as string values. @@ -292,11 +298,11 @@ def index( day of year percentiles (doy_per) Default: 5 (5 days). min_spell_length: int - ``optional`` Minimum spell duration to be taken into account when computing the - sum_of_spell_lengths. + ``optional`` Minimum spell duration to be taken into account when computing + the sum_of_spell_lengths. rolling_window_width: int ``optional`` Window width of the rolling window for indicators such as - `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` # noqa + `{max_of_rolling_sum, max_of_rolling_average, min_of_rolling_sum, min_of_rolling_average}` only_leap_years: bool ``optional`` Option for February 29th (default: False). ignore_Feb29th: bool @@ -307,7 +313,8 @@ def index( Default is "median_unbiased", a.k.a type 8 or method 8. Ignored for non percentile based indices. out_unit: str | None - ``optional`` Output unit for certain indices: "days" or "%" (default: "days"). + ``optional`` Output unit for certain indices: "days" or "%" + (default: "days"). netcdf_version: str | NetcdfVersion ``optional`` NetCDF version to create (default: "NETCDF3_CLASSIC"). user_index: UserIndexDict @@ -315,8 +322,8 @@ def index( See :ref:`Custom indices`. Ignored for ECA&D indices. save_thresholds: bool - ``optional`` True if the thresholds should be saved within the resulting netcdf - file (default: False). + ``optional`` True if the thresholds should be saved within the resulting + netcdf file (default: False). date_event: bool When True the date of the event (such as when a maximum is reached) will be stored in coordinates variables. @@ -327,7 +334,8 @@ def index( sampling_method: str Choose whether the output sampling configured in `slice_mode` is a `groupby` operation or a `resample` operation (as per xarray definitions). - Possible values: ``{"groupby", "resample", "groupby_ref_and_resample_study"}`` + Possible values: + ``{"groupby", "resample", "groupby_ref_and_resample_study"}`` (default: "resample") `groupby_ref_and_resample_study` may only be used when computing the `difference_of_means` (a.k.a the anomaly). @@ -341,7 +349,7 @@ def index( save_percentile: bool DEPRECATED, use save_thresholds instead. - """ + """ # noqa: E501 _setup(callback, callback_percentage_start_value, logs_verbosity) ( index_name, @@ -385,10 +393,12 @@ def index( output_unit = out_unit rolling_window_width = user_index.get("window_width", rolling_window_width) base_period_time_range = user_index.get( - "ref_time_range", base_period_time_range + "ref_time_range", + base_period_time_range, ) elif index_name is not None: - # TODO: [BoundedThreshold] read logical_link from threshold instead + # TODO @bzah: [BoundedThreshold] read logical_link from threshold instead + # https://github.com/cerfacs-globc/icclim/issues/289 logical_link = LogicalLinkRegistry.LOGICAL_AND coef = None standard_index = EcadIndexRegistry.lookup(index_name, no_error=True) @@ -402,10 +412,11 @@ def index( rename = standard_index.short_name output_unit = out_unit or standard_index.output_unit else: - raise InvalidIcclimArgumentError( + msg = ( "You must fill either index_name or user_index" "to compute a climate index." ) + raise InvalidIcclimArgumentError(msg) sampling_frequency = FrequencyRegistry.lookup(slice_mode) if isinstance(threshold, str): threshold = build_configured_threshold(threshold) @@ -420,7 +431,8 @@ def index( # We use groupby instead of resample when there is a single variable that must be # compared to its reference period values. is_compared_to_reference = _must_add_reference_var( - climate_vars_dict, base_period_time_range + climate_vars_dict, + base_period_time_range, ) indicator_name = ( standard_index.short_name if standard_index is not None else indicator.name @@ -435,7 +447,7 @@ def index( ) if base_period_time_range is not None: reference_period = tuple( - map(lambda t: read_date(t).strftime("%m-%d-%Y"), base_period_time_range) + (read_date(t).strftime("%m-%d-%Y") for t in base_period_time_range), ) else: reference_period = None @@ -529,10 +541,11 @@ def _handle_deprecated_params( return index_name, user_index, save_thresholds, doy_window_width -def _setup(callback, callback_start_value, logs_verbosity): +def _setup(callback, callback_start_value, logs_verbosity) -> None: # make xclim input daily check a warning instead of an error - # TODO: it might be safer to feed a context manager which will setup - # and teardown these confs + # TODO @bzah: it might be safer to feed a context manager which will setup + # and teardown these confs + # https://github.com/cerfacs-globc/icclim/issues/289 xclim.set_options(data_validation="warn") # keep attributes through xarray operations xr.set_options(keep_attrs=True) @@ -547,13 +560,12 @@ def _get_unit(output_unit: str | None, da: DataArray) -> str | None: if output_unit is None: warn( "No unit computed or provided for the index was found." - " Use out_unit parameter to add one." + " Use out_unit parameter to add one.", + stacklevel=2, ) return "" - else: - return output_unit - else: - return da_unit + return output_unit + return da_unit def _compute_climate_index( @@ -590,13 +602,16 @@ def _compute_climate_index( result_ds = result_da.to_dataset() if config.save_thresholds: result_ds = xr.merge( - [result_ds, _format_thresholds_for_export(config.climate_variables)] + [result_ds, _format_thresholds_for_export(config.climate_variables)], ) history = _build_history(result_da, config, initial_history, climate_index) - result_ds = _add_ecad_index_metadata( - result_ds, climate_index, history, initial_source, reference + return _add_ecad_index_metadata( + result_ds, + climate_index, + history, + initial_source, + reference, ) - return result_ds def _add_ecad_index_metadata( @@ -607,14 +622,14 @@ def _add_ecad_index_metadata( reference: str, ) -> Dataset: result_ds.attrs.update( - dict( - title=computed_index.standard_name, - references=reference, - institution="Climate impact portal (https://climate4impact.eu)", - history=history, - source=initial_source if initial_source is not None else "", - Conventions="CF-1.6", - ) + { + "title": computed_index.standard_name, + "references": reference, + "institution": "Climate impact portal (https://climate4impact.eu)", + "history": history, + "source": initial_source if initial_source is not None else "", + "Conventions": "CF-1.6", + }, ) try: result_ds.lat.encoding["_FillValue"] = None @@ -634,6 +649,8 @@ def _build_history( initial_history: str | None, indice_computed: Indicator, ) -> str: + from icclim import __version__ as ICCLIM_VERSION + if initial_history is None: # get xclim history initial_history = result_da.attrs[HISTORY_CF_KEY] @@ -641,7 +658,9 @@ def _build_history( # append xclim history initial_history = f"{initial_history}\n{result_da.attrs[HISTORY_CF_KEY]}" del result_da.attrs[HISTORY_CF_KEY] - current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + current_time = dt.datetime.now(tz=dt.timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S", + ) return ( f"{initial_history}\n" f" [{current_time}]" @@ -654,20 +673,19 @@ def _build_history( def _build_threshold( threshold: str | Threshold, doy_window_width: int, - base_period_time_range: Sequence[datetime | str] | None, + base_period_time_range: Sequence[dt.datetime | str] | None, only_leap_years: bool, interpolation: QuantileInterpolation, ) -> Threshold: if isinstance(threshold, Threshold): return threshold - else: - return build_threshold( - threshold, - doy_window_width=doy_window_width, - reference_period=base_period_time_range, - only_leap_years=only_leap_years, - interpolation=interpolation, - ) + return build_threshold( + threshold, + doy_window_width=doy_window_width, + reference_period=base_period_time_range, + only_leap_years=only_leap_years, + interpolation=interpolation, + ) def _format_thresholds_for_export(climate_vars: list[ClimateVariable]) -> Dataset: @@ -678,8 +696,9 @@ def _format_threshold(cf_var: ClimateVariable) -> DataArray: return cf_var.threshold.value.rename(cf_var.name + "_thresholds").reindex() -# TODO move `read_indicator`, `read_threshold`, `read_logical_link`, `read_coef`, -# `read_date_event` to a user_index_parsing module +# TODO @bzah: move `read_indicator`, `read_threshold`, `read_logical_link`, `read_coef`, +# `read_date_event` to a user_index_parsing module +# https://github.com/cerfacs-globc/icclim/issues/289 def read_indicator(user_index: UserIndexDict) -> GenericIndicator: @@ -690,13 +709,13 @@ def read_indicator(user_index: UserIndexDict) -> GenericIndicator: CalcOperationRegistry.SUM: GenericIndicatorRegistry.lookup("Sum"), CalcOperationRegistry.MEAN: GenericIndicatorRegistry.lookup("Average"), CalcOperationRegistry.EVENT_COUNT: GenericIndicatorRegistry.lookup( - "CountOccurrences" + "CountOccurrences", ), - CalcOperationRegistry.MAX_NUMBER_OF_CONSECUTIVE_EVENTS: GenericIndicatorRegistry.lookup( # noqa - "MaxConsecutiveOccurrence" + CalcOperationRegistry.MAX_NUMBER_OF_CONSECUTIVE_EVENTS: GenericIndicatorRegistry.lookup( # noqa: E501 + "MaxConsecutiveOccurrence", ), CalcOperationRegistry.ANOMALY: GenericIndicatorRegistry.lookup( - "DifferenceOfMeans" + "DifferenceOfMeans", ), } if calc_op == CalcOperationRegistry.RUN_SUM: @@ -705,34 +724,31 @@ def read_indicator(user_index: UserIndexDict) -> GenericIndicator: elif user_index["extreme_mode"] == "min": indicator = GenericIndicatorRegistry.lookup("MinOfRollingSum") else: - raise NotImplementedError() + raise NotImplementedError elif calc_op == CalcOperationRegistry.RUN_MEAN: if user_index["extreme_mode"] == "max": indicator = GenericIndicatorRegistry.lookup("MaxOfRollingAverage") elif user_index["extreme_mode"] == "min": indicator = GenericIndicatorRegistry.lookup("MinOfRollingAverage") else: - raise NotImplementedError() + raise NotImplementedError else: indicator = user_index_map.get(calc_op) if indicator is None: - raise InvalidIcclimArgumentError( - f"Unknown user_index calc_operation:" f" '{user_index['calc_operation']}'" - ) + msg = f"Unknown user_index calc_operation: '{user_index['calc_operation']}'" + raise InvalidIcclimArgumentError(msg) return indicator def read_thresholds( - user_index: UserIndexDict, _build_threshold: Callable[[str | Threshold], Threshold] + user_index: UserIndexDict, + _build_threshold: Callable[[str | Threshold], Threshold], ) -> Threshold | None | Sequence[Threshold]: thresh = user_index.get("thresh", None) if thresh is None or isinstance(thresh, Threshold): return thresh - # todo [BoundedThreshold] re-add below code and bind to LogicalLink - # or ( - # isinstance(thresh, (tuple, list)) - # and all(map(lambda th: isinstance(th, Threshold), thresh)) - # ) + # TODO @bzah: [BoundedThreshold] read bounded threshold if thresh is a Sequence + # https://github.com/cerfacs-globc/icclim/issues/289 logical_operation = user_index["logical_operation"] if not isinstance(logical_operation, (tuple, list)): logical_operation = [logical_operation] @@ -766,21 +782,23 @@ def read_threshold( def read_logical_link(user_index: UserIndexDict) -> LogicalLink: - # todo add unit test using it + # TODO @bzah: add unit test using it + # https://github.com/cerfacs-globc/icclim/issues/289 logical_link = user_index.get("link_logical_operations", None) if logical_link is None: return LogicalLinkRegistry.LOGICAL_AND - else: - return LogicalLinkRegistry.lookup(logical_link) + return LogicalLinkRegistry.lookup(logical_link) def read_coef(user_index: UserIndexDict) -> float | None: - # todo add unit test using it + # TODO @bzah: add unit test using it + # https://github.com/cerfacs-globc/icclim/issues/289 return user_index.get("coef", None) def read_date_event(user_index: UserIndexDict) -> float | None: - # todo add unit test using it + # TODO @bzah: add unit test using it + # https://github.com/cerfacs-globc/icclim/issues/289 return user_index.get("date_event", False) @@ -792,5 +810,5 @@ def _must_add_reference_var( is a reference period. Example case: the anomaly of tx(1960-2100) by tx(1960-1990). """ - t = list(climate_vars_dict.values())[0].get("thresholds", None) + t = next(iter(climate_vars_dict.values())).get("thresholds", None) return t is None and len(climate_vars_dict) == 1 and reference_period is not None diff --git a/icclim/models/__init__.py b/src/icclim/models/__init__.py similarity index 100% rename from icclim/models/__init__.py rename to src/icclim/models/__init__.py diff --git a/icclim/models/cf_calendar.py b/src/icclim/models/cf_calendar.py similarity index 89% rename from icclim/models/cf_calendar.py rename to src/icclim/models/cf_calendar.py index 36af1ce0..2b42580c 100644 --- a/icclim/models/cf_calendar.py +++ b/src/icclim/models/cf_calendar.py @@ -20,8 +20,9 @@ def name(self) -> str: return self.aliases[0] -# todo: the whole class might be useless with the latest cftime +# TODO @bzah: the whole class might be useless with the latest cftime # (we don't need our own CfCalendar if we can do `da.time.dt.is_leap_year`) +# https://github.com/cerfacs-globc/icclim/issues/289 class CfCalendarRegistry(Registry[CfCalendar]): """ Calendars known in CF plus some additional custom aliases for convenience. @@ -56,7 +57,8 @@ class CfCalendarRegistry(Registry[CfCalendar]): ) JULIAN = CfCalendar(["julian"], lambda da: _julian_leap(da).values) STANDARD = CfCalendar( - ["standard", "gregorian"], lambda da: _standard_leap(da).values + ["standard", "gregorian"], + lambda da: _standard_leap(da).values, ) # Not sure what to do with none calendar NONE = CfCalendar(["none"], lambda da: _standard_leap(da).values) @@ -68,7 +70,8 @@ def get_item_aliases(item: CfCalendar) -> list[str]: def _proleptic_gregorian_leap(years: DataArray) -> DataArray: return np.logical_or( - years % 400 == 0, np.logical_and(years % 100 != 0, years % 4 == 0) + years % 400 == 0, + np.logical_and(years % 100 != 0, years % 4 == 0), ) diff --git a/icclim/models/climate_variable.py b/src/icclim/models/climate_variable.py similarity index 79% rename from icclim/models/climate_variable.py rename to src/icclim/models/climate_variable.py index 512f5f15..4a9225b5 100644 --- a/icclim/models/climate_variable.py +++ b/src/icclim/models/climate_variable.py @@ -1,25 +1,19 @@ from __future__ import annotations +from collections.abc import Sequence from dataclasses import dataclass -from typing import Any, Sequence +from typing import TYPE_CHECKING, Any -import jinja2 import xarray -from xarray.core.dataarray import DataArray -from icclim.generic_indices.standard_variable import StandardVariable from icclim.generic_indices.threshold import ( PercentileThreshold, Threshold, build_threshold, ) from icclim.icclim_exceptions import InvalidIcclimArgumentError -from icclim.icclim_types import InFileBaseType, InFileLike from icclim.models.constants import REFERENCE_PERIOD_INDEX, UNITS_KEY from icclim.models.frequency import Frequency, FrequencyRegistry -from icclim.models.global_metadata import GlobalMetadata -from icclim.models.standard_index import StandardIndex -from icclim.pre_processing.in_file_dictionary import InFileDictionary from icclim.pre_processing.input_parsing import ( DEFAULT_INPUT_FREQUENCY, build_reference_da, @@ -29,6 +23,16 @@ read_dataset, ) +if TYPE_CHECKING: + import jinja2 + from xarray.core.dataarray import DataArray + + from icclim.generic_indices.standard_variable import StandardVariable + from icclim.icclim_types import InFileBaseType, InFileLike + from icclim.models.global_metadata import GlobalMetadata + from icclim.models.standard_index import StandardIndex + from icclim.pre_processing.in_file_dictionary import InFileDictionary + @dataclass class ClimateVariable: @@ -67,11 +71,11 @@ def build_indicator_metadata( metadata: dict[str, str | dict] = {"threshold": {}} if self.standard_var is None: metadata.update( - dict( - standard_name="unknown_variable", - long_name="unknown variable", - short_name="input", - ) + { + "standard_name": "unknown_variable", + "long_name": "unknown variable", + "short_name": "input", + }, ) else: metadata.update(self.standard_var.get_metadata()) @@ -83,8 +87,8 @@ def build_indicator_metadata( must_run_bootstrap=must_run_bootstrap, jinja_scope=jinja_scope, jinja_env=jinja_env, - ) - } + ), + }, ) return metadata @@ -98,14 +102,15 @@ def build_climate_vars( is_compared_to_reference: bool, ) -> list[ClimateVariable]: if standard_index is not None and len(standard_index.input_variables) > len( - climate_vars_dict + climate_vars_dict, ): - raise InvalidIcclimArgumentError( + msg = ( f"Index {standard_index.short_name} needs" f" {len(standard_index.input_variables)} variables." f" Please provide them with an xarray.Dataset, netCDF file(s) or a" f" zarr store." ) + raise InvalidIcclimArgumentError(msg) acc = [] for i, raw_climate_var in enumerate(climate_vars_dict.items()): if standard_index is not None: @@ -119,10 +124,11 @@ def build_climate_vars( ignore_Feb29th, time_range, standard_var=standard_var, - ) + ), ) if _standard_index_needs_ref( - standard_index, is_compared_to_reference + standard_index, + is_compared_to_reference, ) or _generic_index_needs_ref(standard_index, is_compared_to_reference): standard_var = ( standard_index.input_variables[0] if standard_index is not None else None @@ -159,19 +165,20 @@ def _build_reference_variable( first one. """ if reference_period is None: - raise InvalidIcclimArgumentError( - "Can't build a reference variable without a `base_period_time_range`" - ) - var_name = list(in_files.keys())[0] + msg = "Can't build a reference variable without a `base_period_time_range`" + raise InvalidIcclimArgumentError(msg) + var_name = next(iter(in_files.keys())) if isinstance(in_files, dict): study_ds = read_dataset( - list(in_files.values())[0]["study"], + next(iter(in_files.values()))["study"], standard_var=standard_var, var_name=var_name, ) else: study_ds = read_dataset( - list(in_files.values())[0], standard_var=standard_var, var_name=var_name + next(iter(in_files.values())), + standard_var=standard_var, + var_name=var_name, ) studied_data = build_reference_da( study_ds[var_name], @@ -190,7 +197,7 @@ def _build_reference_variable( "time_encoding": study_ds.time.encoding, }, source_frequency=FrequencyRegistry.lookup( - xarray.infer_freq(studied_data.time) or DEFAULT_INPUT_FREQUENCY + xarray.infer_freq(studied_data.time) or DEFAULT_INPUT_FREQUENCY, ), is_reference=True, ) @@ -204,24 +211,23 @@ def read_in_files( ) -> dict[str, InFileDictionary]: if isinstance(in_files, dict): if var_names is not None: - raise InvalidIcclimArgumentError( + msg = ( "`var_name` must be None when `in_files` is a dictionary." " The dictionary keys are used in place of `var_name`." ) - if isinstance(list(in_files.values())[0], dict): + raise InvalidIcclimArgumentError(msg) + if isinstance(next(iter(in_files.values())), dict): # case of in_files={tasmax: {"study": "tasmax.nc"}} return in_files - else: - # case of in_files={tasmax: "tasmax.nc"} - return _build_in_file_dict( - in_files=list(in_files.values()), - standard_index=standard_index, - threshold=threshold, - var_names=list(in_files.keys()), - ) - else: - # case of in_files="tasmax.nc" and var_names="tasmax" - return _build_in_file_dict(in_files, var_names, threshold, standard_index) + # case of in_files={tasmax: "tasmax.nc"} + return _build_in_file_dict( + in_files=list(in_files.values()), + standard_index=standard_index, + threshold=threshold, + var_names=list(in_files.keys()), + ) + # case of in_files="tasmax.nc" and var_names="tasmax" + return _build_in_file_dict(in_files, var_names, threshold, standard_index) def _build_in_file_dict( @@ -234,10 +240,14 @@ def _build_in_file_dict( standard_index.input_variables[0] if standard_index is not None else None ) input_dataset = read_dataset( - in_files=in_files, standard_var=standard_var, var_name=var_names + in_files=in_files, + standard_var=standard_var, + var_name=var_names, ) var_names = guess_var_names( - ds=input_dataset, standard_index=standard_index, var_names=var_names + ds=input_dataset, + standard_index=standard_index, + var_names=var_names, ) if threshold is not None: if len(var_names) == 1: @@ -245,23 +255,23 @@ def _build_in_file_dict( var_names[0]: { "study": input_dataset[var_names[0]], "thresholds": threshold, - } + }, } if not isinstance(threshold, Sequence): threshold = [threshold] if len(threshold) != len(var_names): # Allow 1 var with multiple thresholds or 1 threshold per var # but no other case - raise InvalidIcclimArgumentError( + msg = ( "There must be as many thresholds as there are variables. There was" f" {len(threshold)} thresholds and {len(var_names)} variables." ) + raise InvalidIcclimArgumentError(msg) return { var_name: {"study": input_dataset[var_name], "thresholds": threshold[i]} for i, var_name in enumerate(var_names) } - else: - return {var_name: {"study": input_dataset[var_name]} for var_name in var_names} + return {var_name: {"study": input_dataset[var_name]} for var_name in var_names} def _build_climate_var( @@ -273,10 +283,13 @@ def _build_climate_var( ) -> ClimateVariable: if isinstance(climate_var_data, dict): study_ds = read_dataset( - climate_var_data["study"], standard_var, climate_var_name + climate_var_data["study"], + standard_var, + climate_var_name, ) - # todo: deprecate climate_var_data.get("per_var_name", None) - # for threshold_var_name + # TODO @bzah: deprecate climate_var_data.get("per_var_name", None) + # in favor of threshold_var_name + # https://github.com/cerfacs-globc/icclim/issues/289 climate_var_thresh = climate_var_data.get("thresholds", None) else: climate_var_data: InFileBaseType @@ -307,7 +320,7 @@ def _build_climate_var( "time_encoding": study_ds.time.encoding, }, source_frequency=FrequencyRegistry.lookup( - xarray.infer_freq(studied_data.time) or DEFAULT_INPUT_FREQUENCY + xarray.infer_freq(studied_data.time) or DEFAULT_INPUT_FREQUENCY, ), ) diff --git a/icclim/models/constants.py b/src/icclim/models/constants.py similarity index 95% rename from icclim/models/constants.py rename to src/icclim/models/constants.py index 684435fe..34270429 100644 --- a/icclim/models/constants.py +++ b/src/icclim/models/constants.py @@ -3,8 +3,6 @@ # fmt: off # flake8: noqa -ICCLIM_VERSION = "6.5.0" - # placeholders for user_index PERCENTILE_THRESHOLD_STAMP = "p" WET_DAY_THRESHOLD = 1 # 1mm @@ -50,7 +48,8 @@ # Mapping of frequencies to generate metadata # copied from xclim and updated. -# todo: we should probably turn that mapping to jinja templates to make it easier to maintain +# TODO @bzah: we should probably turn that mapping to jinja templates to make it easier to maintain +# https://github.com/cerfacs-globc/icclim/issues/289 EN_FREQ_MAPPING = { "YS": "year(s)", "Y": "year(s)", "AS": "year(s)", "A": "year(s)", "QS": "season(s)", "Q": "season(s)", diff --git a/icclim/models/frequency.py b/src/icclim/models/frequency.py similarity index 88% rename from icclim/models/frequency.py rename to src/icclim/models/frequency.py index 1382091e..1061b890 100644 --- a/icclim/models/frequency.py +++ b/src/icclim/models/frequency.py @@ -1,14 +1,14 @@ """ - `icclim.models.frequency` wraps the concept of pandas frequency in order to resample - time series. `slice_mode` parameter of `icclim.index` is always converted to a - `Frequency`. +`icclim.models.frequency` wraps the concept of pandas frequency in order to resample +time series. `slice_mode` parameter of `icclim.index` is always converted to a +`Frequency`. """ from __future__ import annotations import dataclasses import re from datetime import timedelta -from typing import Any, Callable, Sequence +from typing import TYPE_CHECKING, Any, Callable import cftime import numpy as np @@ -18,7 +18,6 @@ from xarray.core.dataarray import DataArray from icclim.icclim_exceptions import InvalidIcclimArgumentError -from icclim.icclim_types import FrequencyLike, Indexer from icclim.models.constants import ( AMJJAS_MONTHS, DJF_MONTHS, @@ -33,6 +32,11 @@ from icclim.models.registry import Registry from icclim.utils import read_date +if TYPE_CHECKING: + from collections.abc import Sequence + + from icclim.icclim_types import FrequencyLike, Indexer + SEASON_ERR_MSG = ( "A season created using `slice_mode` must be made of either" " consecutive integers for months such as [1,2,3] or two date strings" @@ -45,7 +49,10 @@ def get_seasonal_time_updater( - start_month: int, end_month: int, start_day: int = 1, end_day: int = None + start_month: int, + end_month: int, + start_day: int = 1, + end_day: int | None = None, ) -> Callable[[DataArray], tuple[DataArray, DataArray]]: """Seasonal time updater and time bounds creator method generator. Returns a callable of DataArray which will rewrite the time dimension to @@ -72,13 +79,13 @@ def add_time_bounds(da: DataArray) -> tuple[DataArray, DataArray]: new_time_axis = [] first_time = da.time.values[0] for year in da_years: - if start_month > end_month: - year_of_season_end = year + 1 - else: - year_of_season_end = year + year_of_season_end = year + 1 if start_month > end_month else year if isinstance(first_time, cftime.datetime): start = cftime.datetime( - year, start_month, start_day, calendar=first_time.calendar + year, + start_month, + start_day, + calendar=first_time.calendar, ) end = _get_end_date( use_cftime=True, @@ -127,7 +134,7 @@ def add_time_bounds(da: DataArray) -> tuple[DataArray, DataArray]: calendar=date.calendar, ) for date in da.indexes.get("time") - ] + ], ) ends = starts + offset ends = ends - timedelta(days=1) @@ -164,7 +171,7 @@ class Frequency: def build_frequency_kwargs(self) -> dict[str, Any]: """Build kwargs with possible keys in {"freq", "month", "date_bounds"}""" - kwargs = dict(freq=self.pandas_freq) + kwargs = {"freq": self.pandas_freq} if self.indexer is not None: kwargs.update(self.indexer) return kwargs @@ -229,7 +236,7 @@ class FrequencyRegistry(Registry[Frequency]): pandas_freq="AS-APR", accepted_values=["AMJJAS"], adjective="AMJJAS summery", - indexer=dict(month=AMJJAS_MONTHS), + indexer={"month": AMJJAS_MONTHS}, post_processing=get_seasonal_time_updater(AMJJAS_MONTHS[0], AMJJAS_MONTHS[-1]), units="half_year_summers", long_name="AMJJAS season", @@ -242,7 +249,7 @@ class FrequencyRegistry(Registry[Frequency]): pandas_freq="AS-OCT", accepted_values=["ONDJFM"], adjective="ONDJFM wintry", - indexer=dict(month=ONDJFM_MONTHS), + indexer={"month": ONDJFM_MONTHS}, post_processing=get_seasonal_time_updater(ONDJFM_MONTHS[0], ONDJFM_MONTHS[-1]), units="half_year_winters", long_name="ONDJFM season", @@ -255,7 +262,7 @@ class FrequencyRegistry(Registry[Frequency]): pandas_freq="AS-DEC", accepted_values=["DJF"], adjective="DJF wintry", - indexer=dict(month=DJF_MONTHS), + indexer={"month": DJF_MONTHS}, post_processing=get_seasonal_time_updater(DJF_MONTHS[0], DJF_MONTHS[-1]), units="winters", long_name="DJF winter", @@ -268,7 +275,7 @@ class FrequencyRegistry(Registry[Frequency]): pandas_freq="AS-MAR", accepted_values=["MAM"], adjective="MAM springlong", - indexer=dict(month=MAM_MONTHS), + indexer={"month": MAM_MONTHS}, post_processing=get_seasonal_time_updater(MAM_MONTHS[0], MAM_MONTHS[-1]), units="springs", long_name="MAM season", @@ -281,7 +288,7 @@ class FrequencyRegistry(Registry[Frequency]): pandas_freq="AS-JUN", accepted_values=["JJA"], adjective="JJA summery", - indexer=dict(month=JJA_MONTHS), + indexer={"month": JJA_MONTHS}, post_processing=get_seasonal_time_updater(JJA_MONTHS[0], JJA_MONTHS[-1]), units="summers", long_name="JJA season", @@ -294,7 +301,7 @@ class FrequencyRegistry(Registry[Frequency]): pandas_freq="AS-SEP", accepted_values=["SON"], adjective="SON autumnal", - indexer=dict(month=SON_MONTHS), + indexer={"month": SON_MONTHS}, post_processing=get_seasonal_time_updater(SON_MONTHS[0], SON_MONTHS[-1]), units="autumns", long_name="SON season", @@ -313,10 +320,11 @@ def lookup(cls, item: FrequencyLike, no_error: bool = False) -> Frequency | None return _get_frequency_from_iterable(item) if no_error: return None - raise InvalidIcclimArgumentError( + msg = ( f"Unknown frequency {item}." f" Use a Frequency from {FrequencyRegistry.every_aliases()}" ) + raise InvalidIcclimArgumentError(msg) @staticmethod def get_item_aliases(item: Frequency) -> list[str]: @@ -324,7 +332,11 @@ def get_item_aliases(item: Frequency) -> list[str]: def _get_end_date( - use_cftime: bool, year: int, month: int, day: int = None, calendar=None + use_cftime: bool, + year: int, + month: int, + day: int | None = None, + calendar=None, ): delta = timedelta(days=0) if day is None: @@ -345,19 +357,20 @@ def _get_end_date( def _get_frequency_from_string(query: str) -> Frequency: for key, freq in FrequencyRegistry.catalog().items(): if key == query.upper() or query.upper() in map( - str.upper, freq.accepted_values + str.upper, + freq.accepted_values, ): return freq # else assumes it's a pandas frequency (such as "W" or "3MS") try: to_offset(query) # no-op, used to check if it's a valid pandas freq - except ValueError as e: - raise InvalidIcclimArgumentError( + except ValueError as exc: + msg = ( f"Unknown frequency {query}. Use either a" " valid icclim frequency or a valid pandas" - " frequency", - e, + " frequency" ) + raise InvalidIcclimArgumentError(msg, exc) from exc return Frequency( post_processing=get_time_bounds_updater(query), pandas_freq=query, @@ -373,7 +386,7 @@ def _get_frequency_from_string(query: str) -> Frequency: def _is_season_valid(months: list[int]) -> bool: is_valid = True - for i in range(0, len(months) - 1): + for i in range(len(months) - 1): is_valid = is_valid and 0 < months[i] < 13 if months[i] > months[i + 1]: is_valid = is_valid and months[i + 1] == 1 and months[i] == 12 @@ -383,30 +396,31 @@ def _is_season_valid(months: list[int]) -> bool: def _get_frequency_from_iterable( - slice_mode_list: list | tuple[str, Sequence] + slice_mode_list: list | tuple[str, Sequence], ) -> Frequency: if len(slice_mode_list) < 2: - raise InvalidIcclimArgumentError( + msg = ( "Invalid slice_mode format." " When slice_mode is a list, its first element must be a keyword and" " its second a list (e.g `slice_mode=['season', [1,2,3]]` )." ) + raise InvalidIcclimArgumentError(msg) freq_keyword = slice_mode_list[0] if freq_keyword in ["month", "months"]: return _build_frequency_filtered_by_month(slice_mode_list[1]) - elif freq_keyword in ["season", "seasons"]: + if freq_keyword in ["season", "seasons"]: season = slice_mode_list[1] return _build_seasonal_freq(season) - else: - raise InvalidIcclimArgumentError( - f"Unknown frequency {slice_mode_list}." - " The sampling frequency must be one of {'season', 'month'}" - ) + msg = ( + f"Unknown frequency {slice_mode_list}." + " The sampling frequency must be one of {'season', 'month'}" + ) + raise InvalidIcclimArgumentError(msg) def _build_frequency_filtered_by_month(months: Sequence[int]) -> Frequency: return Frequency( - indexer=dict(month=months), + indexer={"month": months}, post_processing=get_time_bounds_updater("MS"), pandas_freq="MS", adjective="monthly", @@ -421,10 +435,9 @@ def _build_frequency_filtered_by_month(months: Sequence[int]) -> Frequency: def _build_seasonal_freq(season: Sequence): if isinstance(season[0], str): return _build_seasonal_frequency_between_dates(season) - elif isinstance(season, tuple) or isinstance(season[0], int): + if isinstance(season, tuple) or isinstance(season[0], int): return _build_seasonal_frequency_for_months(season) - else: - raise NotImplementedError() + raise NotImplementedError def _build_seasonal_frequency_between_dates(season: Sequence[str]) -> Frequency: @@ -434,11 +447,14 @@ def _build_seasonal_frequency_between_dates(season: Sequence[str]) -> Frequency: end_date = read_date(season[1]) begin_formatted = begin_date.strftime("%m-%d") end_formatted = end_date.strftime("%m-%d") - indexer = dict(date_bounds=(begin_formatted, end_formatted)) + indexer = {"date_bounds": (begin_formatted, end_formatted)} return Frequency( indexer=indexer, post_processing=get_seasonal_time_updater( - begin_date.month, end_date.month, begin_date.day, end_date.day + begin_date.month, + end_date.month, + begin_date.day, + end_date.day, ), pandas_freq=f"AS-{MONTHS_MAP[begin_date.month]}", adjective="seasonally", @@ -457,7 +473,7 @@ def _build_seasonal_frequency_for_months(season: tuple | list): season = season[0] + season[1] if not _is_season_valid(season): raise InvalidIcclimArgumentError(SEASON_ERR_MSG) - indexer = dict(month=season) + indexer = {"month": season} return Frequency( indexer=indexer, post_processing=get_seasonal_time_updater(season[0], season[-1]), @@ -479,8 +495,7 @@ def _get_long_name(pandas_freq: str) -> str: freqs = " ".join(freqs) if multiplier: return f"{multiplier[0]} {freqs}" - else: - return freqs + return freqs def _get_delta(pandas_freq: str) -> np.timedelta64: @@ -507,5 +522,4 @@ def _get_delta(pandas_freq: str) -> np.timedelta64: if multiplier: multiplier = int(multiplier[0]) return np.timedelta64(base * multiplier, freq) - else: - return np.timedelta64(base, freq) + return np.timedelta64(base, freq) diff --git a/icclim/models/global_metadata.py b/src/icclim/models/global_metadata.py similarity index 100% rename from icclim/models/global_metadata.py rename to src/icclim/models/global_metadata.py diff --git a/icclim/models/index_config.py b/src/icclim/models/index_config.py similarity index 81% rename from icclim/models/index_config.py rename to src/icclim/models/index_config.py index 7ac5f144..a29415a8 100644 --- a/icclim/models/index_config.py +++ b/src/icclim/models/index_config.py @@ -1,13 +1,14 @@ from __future__ import annotations import dataclasses -from typing import Callable +from typing import TYPE_CHECKING, Callable -from icclim.models.climate_variable import ClimateVariable -from icclim.models.frequency import Frequency -from icclim.models.logical_link import LogicalLink -from icclim.models.netcdf_version import NetcdfVersion -from icclim.models.quantile_interpolation import QuantileInterpolation +if TYPE_CHECKING: + from icclim.models.climate_variable import ClimateVariable + from icclim.models.frequency import Frequency + from icclim.models.logical_link import LogicalLink + from icclim.models.netcdf_version import NetcdfVersion + from icclim.models.quantile_interpolation import QuantileInterpolation @dataclasses.dataclass diff --git a/icclim/models/index_group.py b/src/icclim/models/index_group.py similarity index 91% rename from icclim/models/index_group.py rename to src/icclim/models/index_group.py index 3b2d2a9b..03fc10f1 100644 --- a/icclim/models/index_group.py +++ b/src/icclim/models/index_group.py @@ -11,7 +11,7 @@ class IndexGroup: name: str values: list[IndexGroup] - def __init__(self, name: str, values: list[IndexGroup] = None): + def __init__(self, name: str, values: list[IndexGroup] | None = None): self.name = name if values is None: self.values = [self] @@ -23,17 +23,18 @@ def get_indices(self) -> list[Any]: from icclim.ecad.ecad_indices import EcadIndexRegistry return list( - filter(lambda i: i.group in self.values, EcadIndexRegistry.values()) + filter(lambda i: i.group in self.values, EcadIndexRegistry.values()), ) def __or__(self, right): """Used to compose IndexGroup, e.g., IndexGroup1 | IndexGroup2.""" if isinstance(right, IndexGroup): return IndexGroup(f"{self.name}_{right.name}", [self, right]) - raise NotImplementedError( + msg = ( f"Unexpected type for {right}: {type(right)}." f" An IndexGroup was expected." ) + raise NotImplementedError(msg) def __eq__(self, other): if not isinstance(other, IndexGroup): diff --git a/icclim/models/logical_link.py b/src/icclim/models/logical_link.py similarity index 89% rename from icclim/models/logical_link.py rename to src/icclim/models/logical_link.py index ba3759df..e7bbba27 100644 --- a/icclim/models/logical_link.py +++ b/src/icclim/models/logical_link.py @@ -2,13 +2,15 @@ import dataclasses from functools import reduce -from typing import Callable +from typing import TYPE_CHECKING, Callable import numpy as np -from xarray import DataArray from icclim.models.registry import Registry +if TYPE_CHECKING: + from xarray import DataArray + @dataclasses.dataclass class LogicalLink: @@ -30,12 +32,12 @@ class LogicalLinkRegistry(Registry[LogicalLink]): standard_name="OR", long_name="OR", short_name="OR", - compute=lambda data_list: reduce(np.logical_or, data_list), # type:ignore + compute=lambda data_list: reduce(np.logical_or, data_list), ) LOGICAL_AND = LogicalLink( name="and", standard_name="AND", long_name="AND", short_name="AND", - compute=lambda data_list: reduce(np.logical_and, data_list), # type:ignore + compute=lambda data_list: reduce(np.logical_and, data_list), ) diff --git a/icclim/models/netcdf_version.py b/src/icclim/models/netcdf_version.py similarity index 100% rename from icclim/models/netcdf_version.py rename to src/icclim/models/netcdf_version.py diff --git a/icclim/models/operator.py b/src/icclim/models/operator.py similarity index 85% rename from icclim/models/operator.py rename to src/icclim/models/operator.py index eb91f606..cd683de1 100644 --- a/icclim/models/operator.py +++ b/src/icclim/models/operator.py @@ -1,20 +1,22 @@ from __future__ import annotations import dataclasses -from typing import Callable - -from xarray import DataArray +from typing import TYPE_CHECKING, Callable from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.registry import Registry +if TYPE_CHECKING: + from xarray import DataArray + def _reach_err(_, __): # can't raise error in lambda - raise InvalidIcclimArgumentError( + msg = ( "Reach operator can't be called. Try to fill threshold with an operand" " (e.g. '>=' in '>= 22 degC')." ) + raise InvalidIcclimArgumentError(msg) @dataclasses.dataclass @@ -43,7 +45,7 @@ def get_item_aliases(op) -> list[str]: standard_name="greater_than", aliases=["gt", ">"], operand=">", - compute=lambda da, th: da > th, # noqa + compute=lambda da, th: da > th, ) LOWER = Operator( short_name="lt", @@ -51,7 +53,7 @@ def get_item_aliases(op) -> list[str]: standard_name="lower_than", aliases=["lt", "<"], operand="<", - compute=lambda da, th: da < th, # noqa + compute=lambda da, th: da < th, ) GREATER_OR_EQUAL = Operator( short_name="get", @@ -59,7 +61,7 @@ def get_item_aliases(op) -> list[str]: standard_name="greater_or_equal_to", aliases=["get", "ge", ">=", "=>"], operand=">=", - compute=lambda da, th: da >= th, # noqa + compute=lambda da, th: da >= th, ) LOWER_OR_EQUAL = Operator( short_name="let", @@ -67,7 +69,7 @@ def get_item_aliases(op) -> list[str]: standard_name="lower_or_equal_to", aliases=["let", "le", "<=", "=<"], operand="<=", - compute=lambda da, th: da <= th, # noqa + compute=lambda da, th: da <= th, ) EQUAL = Operator( short_name="e", @@ -75,7 +77,7 @@ def get_item_aliases(op) -> list[str]: standard_name="equal_to", aliases=["e", "equal", "eq", "=", "=="], operand="==", - compute=lambda da, th: da == th, # noqa + compute=lambda da, th: da == th, ) # A None operand means the threshold is reached and a reducer specific computation # is done. Case of excess and deficit (a.k.a gd4, hd17) diff --git a/icclim/models/quantile_interpolation.py b/src/icclim/models/quantile_interpolation.py similarity index 100% rename from icclim/models/quantile_interpolation.py rename to src/icclim/models/quantile_interpolation.py diff --git a/icclim/models/registry.py b/src/icclim/models/registry.py similarity index 96% rename from icclim/models/registry.py rename to src/icclim/models/registry.py index d9f59c99..eaafe201 100644 --- a/icclim/models/registry.py +++ b/src/icclim/models/registry.py @@ -34,10 +34,11 @@ def lookup(cls, query: T | str, no_error: bool = False) -> T: return deepcopy(item) if no_error: return None - raise InvalidIcclimArgumentError( + msg = ( f"Unknown {cls._item_class.__qualname__}: '{query}'. " f"Use one of {cls.every_aliases()}." ) + raise InvalidIcclimArgumentError(msg) @classmethod def every_aliases(cls) -> list[T]: diff --git a/icclim/models/standard_index.py b/src/icclim/models/standard_index.py similarity index 85% rename from icclim/models/standard_index.py rename to src/icclim/models/standard_index.py index 3861c3dc..15e04711 100644 --- a/icclim/models/standard_index.py +++ b/src/icclim/models/standard_index.py @@ -1,10 +1,13 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any, Sequence +from typing import TYPE_CHECKING, Any -import icclim.models.index_group as index_group -from icclim.generic_indices.standard_variable import StandardVariable +if TYPE_CHECKING: + from collections.abc import Sequence + + from icclim.generic_indices.standard_variable import StandardVariable + from icclim.models import index_group @dataclass @@ -16,7 +19,6 @@ class StandardIndex: Attributes ---------- - short_name: str The index name used in the output. compute: Callable @@ -40,7 +42,8 @@ class StandardIndex: group: index_group.IndexGroup input_variables: list[StandardVariable] | None # None when index is generic indicator: Any # Any -> Indicator (circular dep) - # todo: merge qualifiers with group into a Set of qualifiers ? + # TODO @bzah: merge qualifiers with group into a Set of qualifiers ? + # https://github.com/cerfacs-globc/icclim/issues/289 qualifiers: list[str] | None = None source: str | None = None reference: str | None = None @@ -58,7 +61,7 @@ def __str__(self): def __call__(self, *args, **kwargs): self.indicator(*args, **kwargs) - def __eq__(self, other: Any) -> bool: + def __eq__(self, other: object) -> bool: if not isinstance(other, StandardIndex): return False return ( diff --git a/icclim/models/user_index_dict.py b/src/icclim/models/user_index_dict.py similarity index 62% rename from icclim/models/user_index_dict.py rename to src/icclim/models/user_index_dict.py index b2bddb71..fa73a54c 100644 --- a/icclim/models/user_index_dict.py +++ b/src/icclim/models/user_index_dict.py @@ -1,10 +1,13 @@ from __future__ import annotations -import datetime -from typing import Literal, Sequence, TypedDict +from typing import TYPE_CHECKING, Literal, TypedDict -from icclim.models.logical_link import LogicalLink -from icclim.user_indices.calc_operation import CalcOperation, CalcOperationLike +if TYPE_CHECKING: + import datetime as dt + from collections.abc import Sequence + + from icclim.models.logical_link import LogicalLink + from icclim.user_indices.calc_operation import CalcOperation, CalcOperationLike class UserIndexDict(TypedDict, total=False): @@ -19,7 +22,7 @@ class UserIndexDict(TypedDict, total=False): date_event: bool var_type: Literal["t", "p"] | None window_width: int | None - ref_time_range: list[datetime] | list[str] | tuple[str, str] | None + ref_time_range: list[dt.datetime] | list[str] | tuple[str, str] | None # -- deprecated indice_name: str | None diff --git a/icclim/pre_processing/__init__.py b/src/icclim/pre_processing/__init__.py similarity index 100% rename from icclim/pre_processing/__init__.py rename to src/icclim/pre_processing/__init__.py diff --git a/icclim/pre_processing/in_file_dictionary.py b/src/icclim/pre_processing/in_file_dictionary.py similarity index 78% rename from icclim/pre_processing/in_file_dictionary.py rename to src/icclim/pre_processing/in_file_dictionary.py index c5eede20..68a4acf8 100644 --- a/icclim/pre_processing/in_file_dictionary.py +++ b/src/icclim/pre_processing/in_file_dictionary.py @@ -1,9 +1,10 @@ from __future__ import annotations -from typing import TypedDict +from typing import TYPE_CHECKING, TypedDict -from icclim.generic_indices.threshold import Threshold -from icclim.icclim_types import InFileBaseType +if TYPE_CHECKING: + from icclim.generic_indices.threshold import Threshold + from icclim.icclim_types import InFileBaseType class InFileDictionary(TypedDict, total=False): @@ -12,7 +13,6 @@ class InFileDictionary(TypedDict, total=False): Examples -------- - .. code-block:: python in_files = { diff --git a/icclim/pre_processing/input_parsing.py b/src/icclim/pre_processing/input_parsing.py similarity index 78% rename from icclim/pre_processing/input_parsing.py rename to src/icclim/pre_processing/input_parsing.py index 0b55f8b3..576909ef 100644 --- a/icclim/pre_processing/input_parsing.py +++ b/src/icclim/pre_processing/input_parsing.py @@ -1,12 +1,10 @@ from __future__ import annotations -from datetime import datetime -from typing import Hashable, Sequence +from typing import TYPE_CHECKING import numpy as np import xarray as xr import xclim -from pint import Quantity from xarray.core.dataarray import DataArray from xarray.core.dataset import Dataset from xclim.core.units import convert_units_to @@ -16,12 +14,19 @@ StandardVariableRegistry, ) from icclim.icclim_exceptions import InvalidIcclimArgumentError -from icclim.icclim_types import InFileBaseType from icclim.models.cf_calendar import CfCalendarRegistry from icclim.models.constants import UNITS_KEY, VALID_PERCENTILE_DIMENSION -from icclim.models.standard_index import StandardIndex from icclim.utils import get_date_to_iso_format, is_precipitation_amount +if TYPE_CHECKING: + from collections.abc import Hashable, Sequence + from datetime import datetime + + from pint import Quantity + + from icclim.icclim_types import InFileBaseType + from icclim.models.standard_index import StandardIndex + DEFAULT_INPUT_FREQUENCY = "days" @@ -45,7 +50,9 @@ def is_compatible(cls, source: xr.DataArray) -> bool: @classmethod def from_da( - cls, source: xr.DataArray, climatology_bounds: list[str] = None + cls, + source: xr.DataArray, + climatology_bounds: list[str] | None = None, ) -> PercentileDataArray: """Create a PercentileDataArray from a xarray.DataArray. @@ -71,7 +78,8 @@ def from_da( climatology_bounds is None and source.attrs.get("climatology_bounds", None) is None ): - raise ValueError("PercentileDataArray needs a climatology_bounds.") + msg = "PercentileDataArray needs a climatology_bounds." + raise ValueError(msg) per = cls(source) # handle case where da was created with `quantile()` method if "quantile" in source.coords: @@ -81,11 +89,12 @@ def from_da( per.attrs["climatology_bounds"] = clim_bounds if "percentiles" in per.coords: return per - raise ValueError( + msg = ( f"DataArray {source.name} could not be turned into" f" PercentileDataArray. The DataArray must have a" f" 'percentiles' coordinate variable." ) + raise ValueError(msg) def guess_var_names( @@ -95,18 +104,18 @@ def guess_var_names( ) -> list[Hashable]: if var_names is None: return _guess_dataset_var_names(ds=ds, standard_index=standard_index) - elif isinstance(var_names, str): + if isinstance(var_names, str): return [var_names] - elif isinstance(var_names, (list, tuple)): + if isinstance(var_names, (list, tuple)): return var_names - else: - raise NotImplementedError("`var_name` must be a string a list or None.") + msg = "`var_name` must be a string a list or None." + raise NotImplementedError(msg) def read_dataset( in_files: InFileBaseType, standard_var: StandardVariable | None = None, - var_name: str | Sequence[str] = None, + var_name: str | Sequence[str] | None = None, ) -> Dataset: if isinstance(in_files, Dataset): ds = in_files @@ -118,7 +127,9 @@ def read_dataset( # we assumes it's a list of netCDF files # join="override" is used for cases some dimension are a tiny bit different # in different files (was the case with eobs). - ds = xr.open_mfdataset(in_files, parallel=True, join="override") # noqa + # TODO @bzah: change parallel to True when issue is fixed on netcdf4 (py and C) + # https://github.com/Unidata/netcdf4-python/issues/1192 + ds = xr.open_mfdataset(in_files, parallel=False, join="override") elif is_netcdf_path(in_files): ds = xr.open_dataset(in_files) elif is_zarr_path(in_files): @@ -128,12 +139,11 @@ def read_dataset( [ read_dataset(in_file, standard_var, var_name[i]) for i, in_file in enumerate(in_files) - ] + ], ) else: - raise NotImplementedError( - f"`in_files` format {type(in_files)} was not" f" recognized." - ) + msg = f"`in_files` format {type(in_files)} was not recognized." + raise NotImplementedError(msg) return update_to_standard_coords(ds) @@ -141,7 +151,8 @@ def update_to_standard_coords(ds: Dataset) -> Dataset: """ Mutate input ds to use more icclim friendly coordinate names. """ - # TODO see if cf-xarray could replace this + # TODO @bzah: see if cf-xarray could replace this + # https://github.com/cerfacs-globc/icclim/issues/289 if ds.coords.get("t") is not None: ds = ds.rename({"t": "time"}) return ds @@ -160,7 +171,9 @@ def is_glob_path(path: InFileBaseType) -> bool: def standardize_percentile_dim_name(per_da: DataArray) -> DataArray: - # todo [xclim backport] This function could probably be in PercentileDataArray + # TODO @bzah: [xclim backport] This function could probably be in + # PercentileDataArray + # https://github.com/cerfacs-globc/icclim/issues/289 per_dim_name = None for d in VALID_PERCENTILE_DIMENSION: if d in per_da.dims: @@ -169,10 +182,11 @@ def standardize_percentile_dim_name(per_da: DataArray) -> DataArray: # plural handling per_dim_name = f"{d}s" if per_dim_name is None: - raise InvalidIcclimArgumentError( + msg = ( "Percentile data must contain a recognizable percentiles dimension such as" " 'percentiles', 'quantile', 'per' or 'centile'." ) + raise InvalidIcclimArgumentError(msg) per_da = per_da.rename({per_dim_name: "percentiles"}) if "quantile" in per_dim_name: per_da.coords["percentiles"] = per_da.coords["percentiles"] * 100 @@ -180,29 +194,29 @@ def standardize_percentile_dim_name(per_da: DataArray) -> DataArray: def read_clim_bounds( - climatology_bounds: Sequence[str, str] | None, per_da: DataArray + climatology_bounds: Sequence[str, str] | None, + per_da: DataArray, ) -> list[str]: bds = climatology_bounds or per_da.attrs.get("climatology_bounds", None) if len(bds) != 2: - raise InvalidIcclimArgumentError( - "climatology_bounds must be a iterable of length 2." - ) - return [d for d in map(lambda bd: get_date_to_iso_format(bd), bds)] + msg = "climatology_bounds must be a iterable of length 2." + raise InvalidIcclimArgumentError(msg) + return [get_date_to_iso_format(bd) for bd in bds] def _read_dataarray( data: DataArray, standard_var: StandardVariable | None = None, - var_name: str | Sequence[str] = None, + var_name: str | Sequence[str] | None = None, ) -> Dataset: if isinstance(var_name, (tuple, list)): if len(var_name) > 1: - raise InvalidIcclimArgumentError( - "When the `in_file` is a DataArray, there" + msg = ( + "When `in_file` is a DataArray, there" f" can only be one value in `var_name` but var_name was: {var_name} " ) - else: - var_name = var_name[0] + raise InvalidIcclimArgumentError(msg) + var_name = var_name[0] data_name = var_name or standard_var.short_name or None else: data_name = var_name or data.name or "unnamed_var" @@ -210,14 +224,15 @@ def _read_dataarray( def _guess_dataset_var_names( - standard_index: StandardIndex, ds: Dataset + standard_index: StandardIndex, + ds: Dataset, ) -> list[Hashable]: """Try to guess the variable names using the expected kind of variable for the index. """ if standard_index is not None: main_aliases = ", ".join( - map(lambda v: v.short_name, standard_index.input_variables) + (v.short_name for v in standard_index.input_variables), ) error_msg = ( f"Index {standard_index.short_name} needs the following variable(s)" @@ -238,11 +253,9 @@ def _guess_dataset_var_names( if len(climate_var_names) < len(standard_index.input_variables): raise InvalidIcclimArgumentError(error_msg) return climate_var_names - else: - if len(ds.data_vars) == 1: - return [get_name_of_first_var(ds)] - else: - return find_standard_vars(ds) + if len(ds.data_vars) == 1: + return [get_name_of_first_var(ds)] + return find_standard_vars(ds) def find_standard_vars(ds: Dataset) -> list[Hashable]: @@ -257,7 +270,8 @@ def guess_input_type(data: DataArray) -> StandardVariable | None: cf_input = StandardVariableRegistry.lookup(str(data.name), no_error=True) if cf_input is None and data.attrs.get("standard_name", None) is not None: cf_input = StandardVariableRegistry.lookup( - data.attrs.get("standard_name"), no_error=True + data.attrs.get("standard_name"), + no_error=True, ) if cf_input is None: return None @@ -276,11 +290,12 @@ def build_studied_data( da = original_da.sel(time=slice(time_range[0], time_range[1])) check_time_range_post_validity(da, original_da, "time_range", time_range) if len(da.time) == 0: - raise InvalidIcclimArgumentError( + msg = ( f"The given `time_range` {time_range} is out of the dataset time" f" period: {original_da.time.min().dt.floor('D').values}" f" - {original_da.time.max().dt.floor('D').values}." ) + raise InvalidIcclimArgumentError(msg) else: da = original_da if ignore_Feb29th: @@ -289,49 +304,48 @@ def build_studied_data( da.attrs[UNITS_KEY] = standard_var.default_units if is_precipitation_amount(da): da = xclim.core.units.amount2rate(da) - da = da.chunk("auto") - return da + return da.chunk("auto") def check_time_range_pre_validity(key: str, tr: Sequence[datetime | str]) -> None: if len(tr) != 2: - raise InvalidIcclimArgumentError( + msg = ( f"The given `{key}` {tr}" f" has {len(tr)} elements." f" It must have exactly 2 dates." ) + raise InvalidIcclimArgumentError(msg) def check_time_range_post_validity(da, original_da, key: str, tr: list) -> None: if len(da.time) == 0: - raise InvalidIcclimArgumentError( + msg = ( f"The given `{key}` {tr} is out of the sample time bounds:" f" {original_da.time.min().dt.floor('D').values}" f" - {original_da.time.max().dt.floor('D').values}." ) + raise InvalidIcclimArgumentError(msg) def _is_alias_valid(ds, alias) -> bool: - for ds_var in ds.data_vars: - if str(ds_var).upper() == alias.upper(): - return True - return False + return any(str(ds_var).upper() == alias.upper() for ds_var in ds.data_vars) def _get_actual_name(ds, alias) -> str: for ds_var in ds.data_vars: if str(ds_var).upper() == alias.upper(): return str(ds_var) - raise KeyError(f"Could not find {alias} in dataset.") + msg = f"Could not find {alias} in dataset." + raise KeyError(msg) def get_name_of_first_var(ds: Dataset) -> str: - return str(ds.data_vars[list(ds.data_vars.keys())[0]].name) + return str(ds.data_vars[next(iter(ds.data_vars.keys()))].name) def is_dataset_path(query: Sequence | str) -> bool: if isinstance(query, (tuple, list)): - return all(map(lambda q: is_netcdf_path(q), query)) + return all(is_netcdf_path(q) for q in query) return is_zarr_path(query) or is_glob_path(query) or is_netcdf_path(query) @@ -341,9 +355,8 @@ def reduce_only_leap_years(da: DataArray) -> DataArray: if val.time.dt.dayofyear.max() == 366: reduced_list.append(val) if not reduced_list: - raise InvalidIcclimArgumentError( - "No leap year in current dataset. Do not use `only_leap_years` parameter." - ) + msg = "No leap year in current dataset. Do not use `only_leap_years` parameter." + raise InvalidIcclimArgumentError(msg) return xr.concat(reduced_list, "time") @@ -363,9 +376,12 @@ def read_threshold_DataArray( if threshold_min_value: if isinstance(threshold_min_value, str): threshold_min_value = convert_units_to( - threshold_min_value, thresh_da, context="hydro" + threshold_min_value, + thresh_da, + context="hydro", ) - # todo in prcptot the replacing value (np.nan) needs to be 0 + # TODO @bzah: in prcptot the replacing value (np.nan) needs to be 0 + # https://github.com/cerfacs-globc/icclim/issues/289 built_value = thresh_da.where(thresh_da > threshold_min_value, np.nan) else: built_value = thresh_da @@ -386,16 +402,21 @@ def build_reference_da( get_date_to_iso_format(x) for x in base_period_time_range ] reference = original_da.sel( - time=slice(base_period_time_range[0], base_period_time_range[1]) + time=slice(base_period_time_range[0], base_period_time_range[1]), ) check_time_range_post_validity( - reference, original_da, "base_period_time_range", base_period_time_range + reference, + original_da, + "base_period_time_range", + base_period_time_range, ) if only_leap_years: reference = reduce_only_leap_years(original_da) if percentile_min_value is not None: percentile_min_value = convert_units_to( - str(percentile_min_value), reference, context="hydro" + str(percentile_min_value), + reference, + context="hydro", ) reference = reference.where(reference >= percentile_min_value, np.nan) return reference diff --git a/icclim/pre_processing/rechunk.py b/src/icclim/pre_processing/rechunk.py similarity index 90% rename from icclim/pre_processing/rechunk.py rename to src/icclim/pre_processing/rechunk.py index b5818183..1d2543b8 100644 --- a/icclim/pre_processing/rechunk.py +++ b/src/icclim/pre_processing/rechunk.py @@ -2,6 +2,7 @@ import contextlib import copy +from typing import TYPE_CHECKING import dask import fsspec @@ -11,13 +12,15 @@ from fsspec import AbstractFileSystem from fsspec.implementations.local import LocalFileSystem from rechunker import rechunk -from xarray.core.dataarray import DataArray -from xarray.core.dataset import Dataset from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.icclim_logger import IcclimLogger from icclim.pre_processing.input_parsing import is_zarr_path, read_dataset +if TYPE_CHECKING: + from xarray.core.dataarray import DataArray + from xarray.core.dataset import Dataset + TMP_STORE_1 = "icclim-tmp-store-1.zarr" TMP_STORE_2 = "icclim-tmp-store-2.zarr" DEFAULT_DASK_CONF = { @@ -27,6 +30,8 @@ "distributed.worker.memory.terminate": "0.98", } +LOCAL_FILE_SYSTEM = LocalFileSystem() + logger = IcclimLogger.get_instance() @@ -35,7 +40,8 @@ def _get_mem_limit(factor: float = 0.9) -> int: # https://github.com/pangeo-data/rechunker/issues/54#issuecomment-700748875 # we should limit rechunk mem usage to around 0.9 and avoid spilling to disk if factor > 1 or factor < 0: - raise ValueError(f"factor was {factor} but, it must be between 0 and 1.") + msg = f"factor was {factor} but, it must be between 0 and 1." + raise ValueError(msg) try: import distributed @@ -57,7 +63,7 @@ def create_optimized_zarr_store( target_zarr_store_name: str = "icclim-target-store.zarr", keep_target_store: bool = False, chunking: dict[str, int] | None = None, - filesystem: str | AbstractFileSystem = LocalFileSystem(), + filesystem: str | AbstractFileSystem = LOCAL_FILE_SYSTEM, ) -> Dataset: """ -- EXPERIMENTAL FEATURE -- @@ -86,7 +92,6 @@ def create_optimized_zarr_store( Examples -------- - .. code-block:: python import icclim @@ -101,7 +106,6 @@ def create_optimized_zarr_store( Parameters ---------- - in_files : str | list[str] | Dataset | DataArray Absolute path(s) to NetCDF dataset(s), including OPeNDAP URLs, or path to zarr store, or xarray.Dataset or xarray.DataArray. @@ -131,7 +135,10 @@ def create_optimized_zarr_store( if isinstance(filesystem, str): filesystem = fsspec.filesystem("file") _remove_stores( - TMP_STORE_1, TMP_STORE_2, target_zarr_store_name, filesystem=filesystem + TMP_STORE_1, + TMP_STORE_2, + target_zarr_store_name, + filesystem=filesystem, ) yield _unsafe_create_optimized_zarr_store( in_files, @@ -150,10 +157,8 @@ def create_optimized_zarr_store( def _remove_stores(*stores, filesystem: AbstractFileSystem): for s in stores: - try: + with contextlib.suppress(FileNotFoundError): filesystem.rm(s, recursive=True, maxdepth=100) - except FileNotFoundError: - pass def _unsafe_create_optimized_zarr_store( @@ -171,17 +176,16 @@ def _unsafe_create_optimized_zarr_store( # drop all non essential data variables ds = ds.drop_vars(filter(lambda v: v not in var_name, ds.data_vars.keys())) if len(ds.data_vars.keys()) == 0: - raise InvalidIcclimArgumentError( - f"The variable(s) {var_name} were not found in the dataset." - ) + msg = f"The variable(s) {var_name} were not found in the dataset." + raise InvalidIcclimArgumentError(msg) if _is_rechunking_unnecessary(ds, chunking): - raise InvalidIcclimArgumentError( + msg = ( f"The given input is already chunked following {chunking}." f" It's unnecessary to rechunk data with" f" `create_optimized_zarr_store` here." ) - elif chunking is None: - chunking = _build_default_chunking(ds) + raise InvalidIcclimArgumentError(msg) + chunking = _build_default_chunking(ds) # It seems rechunker performs better when the dataset is first converted # to a zarr store, without rechunking anything. if not is_ds_zarr: @@ -223,5 +227,4 @@ def _is_rechunking_unnecessary(ds: Dataset, chunking: dict[str, int] | None) -> cp = copy.deepcopy(ds.chunks) if chunking is None: return len(ds.chunks["time"]) == 1 - else: - return ds.chunk(chunking).chunks == cp + return ds.chunk(chunking).chunks == cp diff --git a/icclim/tests/__init__.py b/src/icclim/user_indices/__init__.py similarity index 100% rename from icclim/tests/__init__.py rename to src/icclim/user_indices/__init__.py diff --git a/icclim/user_indices/calc_operation.py b/src/icclim/user_indices/calc_operation.py similarity index 82% rename from icclim/user_indices/calc_operation.py rename to src/icclim/user_indices/calc_operation.py index 39f45ee3..865c8a78 100644 --- a/icclim/user_indices/calc_operation.py +++ b/src/icclim/user_indices/calc_operation.py @@ -1,7 +1,8 @@ from __future__ import annotations import dataclasses -from typing import Hashable, Literal +from collections.abc import Hashable +from typing import Literal from icclim.models.registry import Registry @@ -27,7 +28,8 @@ def __hash__(self): class CalcOperationRegistry(Registry[CalcOperation]): - # todo remove class once deprecation is finished (v6.1 ?) + # TODO @bzah: remove class once deprecation is finished (v6.1 ?) + # https://github.com/cerfacs-globc/icclim/issues/289 _item_class = CalcOperation MAX = CalcOperation("max") MIN = CalcOperation("min") diff --git a/icclim/utils.py b/src/icclim/utils.py similarity index 80% rename from icclim/utils.py rename to src/icclim/utils.py index 08e0cb50..f70f3f65 100644 --- a/icclim/utils.py +++ b/src/icclim/utils.py @@ -3,14 +3,17 @@ import re import warnings from datetime import datetime +from typing import TYPE_CHECKING import dateparser -import pint -import xarray as xr import xclim from icclim.icclim_exceptions import InvalidIcclimArgumentError +if TYPE_CHECKING: + import pint + import xarray as xr + PR_AMOUNT_STANDARD_NAME = "thickness_of_rainfall_amount" @@ -19,21 +22,25 @@ def read_date(in_date: str | datetime) -> datetime: return in_date date = dateparser.parse(in_date) if date is None: - raise InvalidIcclimArgumentError( + msg = ( f"The date {in_date} does not have a valid format." " You can use various formats such as '2 december', '02-12'," " '1994-12-02'..." ) + raise InvalidIcclimArgumentError(msg) return date def get_date_to_iso_format(in_date: str | datetime) -> str: if isinstance(in_date, str): if re.match(r"^\d{4}$", in_date): - warnings.warn(f"{in_date} is transformed into {in_date}-01-01") + warnings.warn( + f"{in_date} is transformed into {in_date}-01-01", + stacklevel=2, + ) in_date += "-01-01" if re.match(r"^\d{4}-\d{2}$", in_date): - warnings.warn(f"{in_date} is transformed into {in_date}-01") + warnings.warn(f"{in_date} is transformed into {in_date}-01", stacklevel=2) in_date += "-01" in_date = read_date(in_date) return in_date.strftime("%Y-%m-%d") @@ -41,7 +48,7 @@ def get_date_to_iso_format(in_date: str | datetime) -> str: def is_number_sequence(values) -> bool: return isinstance(values, (tuple, list)) and all( - map(lambda x: isinstance(x, (float, int)), values) + (isinstance(x, (float, int)) for x in values), ) diff --git a/icclim/user_indices/__init__.py b/tests/__init__.py similarity index 100% rename from icclim/user_indices/__init__.py rename to tests/__init__.py diff --git a/icclim/tests/test_cf_calendar.py b/tests/test_cf_calendar.py similarity index 93% rename from icclim/tests/test_cf_calendar.py rename to tests/test_cf_calendar.py index be1e0340..bb8b9c74 100644 --- a/icclim/tests/test_cf_calendar.py +++ b/tests/test_cf_calendar.py @@ -4,7 +4,6 @@ import pandas as pd import pytest import xarray as xr - from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.cf_calendar import CfCalendarRegistry @@ -46,17 +45,17 @@ def test_ALL_LEAP(self): def test_PROLEPTIC_GREGORIAN(self): res_1 = CfCalendarRegistry.PROLEPTIC_GREGORIAN.is_leap( - xr.DataArray(np.asarray([40, 1600])) + xr.DataArray(np.asarray([40, 1600])), ) res_2 = CfCalendarRegistry.PROLEPTIC_GREGORIAN.is_leap( - xr.DataArray(np.asarray([42, 1500, 1700])) + xr.DataArray(np.asarray([42, 1500, 1700])), ) np.testing.assert_array_equal(True, res_1) np.testing.assert_array_equal(False, res_2) def test_JULIAN(self): res_1 = CfCalendarRegistry.JULIAN.is_leap( - xr.DataArray(np.asarray([40, 1500, 1600, 1700])) + xr.DataArray(np.asarray([40, 1500, 1600, 1700])), ) res_2 = CfCalendarRegistry.JULIAN.is_leap(xr.DataArray(np.asarray([42]))) np.testing.assert_array_equal(True, res_1) diff --git a/icclim/tests/test_ecad_indices.py b/tests/test_ecad_indices.py similarity index 95% rename from icclim/tests/test_ecad_indices.py rename to tests/test_ecad_indices.py index 1887cf6b..f10f5b3f 100644 --- a/icclim/tests/test_ecad_indices.py +++ b/tests/test_ecad_indices.py @@ -1,14 +1,13 @@ from __future__ import annotations import pytest - from icclim.ecad.ecad_indices import EcadIndexRegistry from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.standard_index import StandardIndex def test_listing(): - res = EcadIndexRegistry.list() + res = EcadIndexRegistry.to_list() indices = [ k for k, v in EcadIndexRegistry.__dict__.items() if isinstance(v, StandardIndex) ] diff --git a/icclim/tests/test_frequency.py b/tests/test_frequency.py similarity index 99% rename from icclim/tests/test_frequency.py rename to tests/test_frequency.py index 74b33add..1524b52b 100644 --- a/icclim/tests/test_frequency.py +++ b/tests/test_frequency.py @@ -4,10 +4,10 @@ import numpy as np import pandas as pd import pytest - from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.frequency import FrequencyRegistry, get_seasonal_time_updater -from icclim.tests.testing_utils import stub_tas + +from tests.testing_utils import stub_tas class Test_build_frequency_over_frequency: diff --git a/icclim/tests/test_generated_api.py b/tests/test_generated_api.py similarity index 78% rename from icclim/tests/test_generated_api.py rename to tests/test_generated_api.py index 280149e3..114aeee5 100644 --- a/icclim/tests/test_generated_api.py +++ b/tests/test_generated_api.py @@ -1,12 +1,12 @@ from __future__ import annotations -from datetime import datetime +import datetime as dt +from typing import TYPE_CHECKING from unittest.mock import MagicMock, patch +import icclim import numpy as np import pytest - -import icclim from icclim.ecad.ecad_indices import EcadIndexRegistry from icclim.generic_indices.generic_indicators import GenericIndicatorRegistry from icclim.generic_indices.threshold import build_threshold @@ -16,21 +16,24 @@ from icclim.models.frequency import FrequencyRegistry from icclim.models.netcdf_version import NetcdfVersionRegistry from icclim.models.quantile_interpolation import QuantileInterpolationRegistry -from icclim.models.standard_index import StandardIndex -from icclim.tests.testing_utils import stub_tas from icclim.user_indices.calc_operation import CalcOperation, CalcOperationRegistry -DEFAULT_ARGS = dict( - in_files="pouet.nc", - var_name=None, - slice_mode=FrequencyRegistry.YEAR, - time_range=None, - out_file=None, - ignore_Feb29th=False, - netcdf_version=NetcdfVersionRegistry.NETCDF4, - logs_verbosity=VerbosityRegistry.LOW, - date_event=False, -) +from tests.testing_utils import stub_tas + +if TYPE_CHECKING: + from icclim.models.standard_index import StandardIndex + +DEFAULT_ARGS = { + "in_files": "pouet.nc", + "var_name": None, + "slice_mode": FrequencyRegistry.YEAR, + "time_range": None, + "out_file": None, + "ignore_Feb29th": False, + "netcdf_version": NetcdfVersionRegistry.NETCDF4, + "logs_verbosity": VerbosityRegistry.LOW, + "date_event": False, +} def build_expected_args(index: StandardIndex): @@ -44,13 +47,13 @@ def build_expected_args(index: StandardIndex): "only_leap_years": False, "interpolation": QuantileInterpolationRegistry.MEDIAN_UNBIASED.name, "save_thresholds": False, - } + }, ) elif REFERENCE_PERIOD_INDEX in qualifiers: expected_call_args.update( { "base_period_time_range": None, - } + }, ) if index.threshold is not None: if isinstance(index.threshold, str): @@ -72,18 +75,16 @@ def build_expected_args(index: StandardIndex): @patch("icclim.index") def test_generated_api(generic_index_fun_mock: MagicMock): for i in EcadIndexRegistry.values(): - # print(i) # GIVEN - api_index_fun = eval(f"icclim.{i.short_name.lower()}") + api_index_fun = eval(f"icclim.{i.short_name.lower()}") # noqa: PGH001 # WHEN api_index_fun(**DEFAULT_ARGS) # THEN expected_call_args = build_expected_args(i) generic_index_fun_mock.assert_called_with(**expected_call_args) for g in GenericIndicatorRegistry.values(): - print(g) # GIVEN - api_index_fun = eval(f"icclim.{g.name.lower()}") + api_index_fun = eval(f"icclim.{g.name.lower()}") # noqa: PGH001 # WHEN api_index_fun(**DEFAULT_ARGS) generic_index_fun_mock.assert_called() @@ -91,33 +92,33 @@ def test_generated_api(generic_index_fun_mock: MagicMock): @patch("icclim.index") def test_custom_index(index_fun_mock: MagicMock): - user_index_args = dict( - in_files="pouet_file.nc", - var_name=None, - slice_mode=FrequencyRegistry.YEAR, - time_range=None, - out_file=None, - base_period_time_range=None, - only_leap_years=False, - ignore_Feb29th=False, - out_unit=None, - netcdf_version=NetcdfVersionRegistry.NETCDF4, - logs_verbosity=VerbosityRegistry.LOW, - doy_window_width=5, - save_thresholds=False, - date_event=False, - sampling_method="resample", - min_spell_length=6, - rolling_window_width=5, - interpolation="median_unbiased", - user_index={ + user_index_args = { + "in_files": "pouet_file.nc", + "var_name": None, + "slice_mode": FrequencyRegistry.YEAR, + "time_range": None, + "out_file": None, + "base_period_time_range": None, + "only_leap_years": False, + "ignore_Feb29th": False, + "out_unit": None, + "netcdf_version": NetcdfVersionRegistry.NETCDF4, + "logs_verbosity": VerbosityRegistry.LOW, + "doy_window_width": 5, + "save_thresholds": False, + "date_event": False, + "sampling_method": "resample", + "min_spell_length": 6, + "rolling_window_width": 5, + "interpolation": "median_unbiased", + "user_index": { "index_name": "pouet", "calc_operation": "nb_events", "logical_operation": "gt", "thresh": 0, "date_event": True, }, - ) + } icclim.custom_index(**user_index_args) index_fun_mock.assert_called_with(**user_index_args) @@ -156,7 +157,7 @@ def test_txx__months_slice_mode(): # integration test @pytest.mark.parametrize( - "operator, expectation_year_1, expectation_year_2", + ("operator", "expectation_year_1", "expectation_year_2"), [ (CalcOperationRegistry.MIN, 303.15, 280.15), (CalcOperationRegistry.MAX, 303.15, 280.15), @@ -168,7 +169,9 @@ def test_txx__months_slice_mode(): ], ) def test_custom_index__season_slice_mode( - operator: CalcOperation, expectation_year_1, expectation_year_2 + operator: CalcOperation, + expectation_year_1, + expectation_year_2, ): tas = stub_tas(275.0) tas.loc[{"time": "2043-01-01"}] = 303.15 @@ -193,14 +196,16 @@ def test_custom_index__season_slice_mode( # integration test @pytest.mark.parametrize( - "operator, expectation_year_1, expectation_year_2", + ("operator", "expectation_year_1", "expectation_year_2"), [ (CalcOperationRegistry.RUN_MEAN, 275.0, 276.0), (CalcOperationRegistry.RUN_SUM, 1925.0, 1932.0), ], ) def test_custom_index_run_algos__season_slice_mode( - operator, expectation_year_1, expectation_year_2 + operator, + expectation_year_1, + expectation_year_2, ): tas = stub_tas(275.0) tas.loc[{"time": "2043-12-01"}] = 282.0 @@ -243,7 +248,10 @@ def test_custom_index_anomaly__error_(): icclim.custom_index( in_files=tas, slice_mode=["season", [12, 1]], - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2014, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], user_index={ "index_name": "anomaly", "calc_operation": CalcOperationRegistry.ANOMALY, @@ -257,7 +265,10 @@ def test_custom_index_anomaly__datetime_ref_period(): res = icclim.custom_index( in_files=tas, slice_mode=["season", [12, 1]], - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2014, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], sampling_method="groupby_ref_and_resample_study", user_index={ "index_name": "anomaly", @@ -279,7 +290,10 @@ def test_custom_index_anomaly__groupby_and_resample_month(): res = icclim.custom_index( in_files=tas, slice_mode="month", - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2014, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], sampling_method="groupby_ref_and_resample_study", user_index={ "index_name": "anomaly", @@ -295,7 +309,10 @@ def test_custom_index_anomaly__groupby_and_resample_year(): res = icclim.custom_index( in_files=tas, slice_mode="year", - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2014, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], sampling_method="groupby_ref_and_resample_study", user_index={ "index_name": "anomaly", @@ -311,7 +328,10 @@ def test_custom_index_anomaly__groupby_and_resample_day(): res = icclim.custom_index( in_files=tas, slice_mode="day", - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2014, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], sampling_method="groupby_ref_and_resample_study", user_index={ "index_name": "anomaly", @@ -328,7 +348,10 @@ def test_custom_index_anomaly__groupby_and_resample_hour(): icclim.custom_index( in_files=tas, slice_mode="hour", - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2014, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], sampling_method="groupby_ref_and_resample_study", user_index={ "index_name": "anomaly", @@ -343,7 +366,10 @@ def test_custom_index_anomaly__grouby_season(): res = icclim.custom_index( in_files=tas, slice_mode=["season", [12, 1]], - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2042, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], sampling_method="groupby", user_index={ "index_name": "anomaly", @@ -360,7 +386,10 @@ def test_custom_index_anomaly__grouby_month(): res = icclim.custom_index( in_files=tas, slice_mode="month", - base_period_time_range=[datetime(2042, 1, 1), datetime(2044, 12, 31)], + base_period_time_range=[ + dt.datetime(2042, 1, 1, tzinfo=dt.timezone.utc), + dt.datetime(2044, 12, 31, tzinfo=dt.timezone.utc), + ], sampling_method="groupby", user_index={ "index_name": "anomaly", diff --git a/icclim/tests/test_index_group.py b/tests/test_index_group.py similarity index 99% rename from icclim/tests/test_index_group.py rename to tests/test_index_group.py index 84d9c97d..b0f8542c 100644 --- a/icclim/tests/test_index_group.py +++ b/tests/test_index_group.py @@ -1,7 +1,6 @@ from __future__ import annotations import pytest - from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.index_group import IndexGroupRegistry diff --git a/icclim/tests/test_input_parsing.py b/tests/test_input_parsing.py similarity index 81% rename from icclim/tests/test_input_parsing.py rename to tests/test_input_parsing.py index 4a9a66b6..9fb84443 100644 --- a/icclim/tests/test_input_parsing.py +++ b/tests/test_input_parsing.py @@ -1,13 +1,13 @@ from __future__ import annotations -import os +import contextlib import shutil +from pathlib import Path import numpy as np import pandas as pd import pytest import xarray as xr - from icclim.ecad.ecad_indices import EcadIndexRegistry from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.constants import UNITS_KEY @@ -25,16 +25,16 @@ def test_update_to_standard_coords(): { "pouet": xr.DataArray( data=np.full(10, 42).reshape((10, 1, 1)), - coords=dict( - latitude=[42], - longitude=[42], - t=pd.date_range("2042-01-01", periods=10, freq="D"), - ), + coords={ + "latitude": [42], + "longitude": [42], + "t": pd.date_range("2042-01-01", periods=10, freq="D"), + }, dims=["t", "latitude", "longitude"], name="pr", attrs={UNITS_KEY: "kg m-2 d-1"}, - ) - } + ), + }, ) # WHEN res = update_to_standard_coords(ds) @@ -43,34 +43,34 @@ def test_update_to_standard_coords(): class Test_ReadDataset: - OUTPUT_NC_FILE = "tmp.nc" - OUTPUT_NC_FILE_2 = "tmp-2.nc" - OUTPUT_ZARR_STORE = "tmp.zarr" - OUTPUT_UNKNOWN_FORMAT = "tmp.cacahuete" + OUTPUT_NC_FILE = Path("tmp.nc") + OUTPUT_NC_FILE_2 = Path("tmp-2.nc") + OUTPUT_ZARR_STORE = Path("tmp.zarr") + OUTPUT_UNKNOWN_FORMAT = Path("tmp.cacahuete") pr_da = None tas_da = None @pytest.fixture(autouse=True) - def cleanup(self): + def _cleanup(self): # -- setup self.pr_da = xr.DataArray( data=np.full(10, 42).reshape((10, 1, 1)), - coords=dict( - latitude=[42], - longitude=[42], - time=pd.date_range("2042-01-01", periods=10, freq="D"), - ), + coords={ + "latitude": [42], + "longitude": [42], + "time": pd.date_range("2042-01-01", periods=10, freq="D"), + }, dims=["time", "latitude", "longitude"], name="pr", attrs={UNITS_KEY: "kg m-2 d-1"}, ) self.tas_da = xr.DataArray( data=np.full(10, 42).reshape((10, 1, 1)), - coords=dict( - latitude=[42], - longitude=[42], - t=pd.date_range("2042-01-01", periods=10, freq="D"), - ), + coords={ + "latitude": [42], + "longitude": [42], + "t": pd.date_range("2042-01-01", periods=10, freq="D"), + }, dims=["t", "latitude", "longitude"], name="tas", attrs={UNITS_KEY: "degC"}, @@ -83,10 +83,8 @@ def cleanup(self): self.OUTPUT_NC_FILE_2, self.OUTPUT_UNKNOWN_FORMAT, ]: - try: - os.remove(f) - except FileNotFoundError: - pass + with contextlib.suppress(FileNotFoundError): + f.unlink() def test_read_dataset_xr_DataArray__simple(self): # WHEN @@ -113,7 +111,7 @@ def test_read_dataset__netcdf_success(self): ds = xr.Dataset({"pouet": self.pr_da}) ds.to_netcdf(self.OUTPUT_NC_FILE) # WHEN - ds_res = read_dataset(self.OUTPUT_NC_FILE) + ds_res = read_dataset(str(self.OUTPUT_NC_FILE)) # THEN xr.testing.assert_equal(ds_res.pouet, ds.pouet) @@ -123,7 +121,7 @@ def test_read_dataset__multi_netcdf_success(self): ds.to_netcdf(self.OUTPUT_NC_FILE) ds.rename({"pouet": "patapouet"}).to_netcdf(self.OUTPUT_NC_FILE_2) # WHEN - ds_res = read_dataset([self.OUTPUT_NC_FILE, self.OUTPUT_NC_FILE_2]) + ds_res = read_dataset([str(self.OUTPUT_NC_FILE), str(self.OUTPUT_NC_FILE_2)]) # THEN xr.testing.assert_equal(ds_res.pouet, ds.pouet) xr.testing.assert_equal(ds_res.patapouet, ds.pouet) @@ -133,7 +131,7 @@ def test_read_dataset__zarr_store_success(self): ds = xr.Dataset({"pouet": self.pr_da}) ds.to_zarr(self.OUTPUT_ZARR_STORE) # WHEN - ds_res = read_dataset(self.OUTPUT_ZARR_STORE) + ds_res = read_dataset(str(self.OUTPUT_ZARR_STORE)) # THEN xr.testing.assert_equal(ds_res.pouet, ds.pouet) @@ -141,14 +139,14 @@ def test_read_dataset__not_implemented_error(self): # THEN with pytest.raises(NotImplementedError): # WHEN - read_dataset(42) # noqa + read_dataset(42) def test_read_dataset(self): # GIVEN ds = xr.Dataset({"tas": self.tas_da}) ds.to_netcdf(self.OUTPUT_NC_FILE) # WHEN - res_ds = read_dataset(self.OUTPUT_NC_FILE) + res_ds = read_dataset(str(self.OUTPUT_NC_FILE)) # THEN # asserts variable names are the ones in the actual DataArray/Datasets assert "tas" in res_ds.data_vars @@ -161,7 +159,8 @@ def test_read_dataset__with_percentiles(self): per.coords["percentiles"] = 42 per = per.rename("tontontonthetatilotetatoux").expand_dims("percentiles") per = PercentileDataArray.from_da( - per, climatology_bounds=["1994-12-02", "1999-01-01"] + per, + climatology_bounds=["1994-12-02", "1999-01-01"], ) ds["tontontonthetatilotetatoux"] = per # WHEN diff --git a/icclim/tests/test_main.py b/tests/test_main.py similarity index 88% rename from icclim/tests/test_main.py rename to tests/test_main.py index c2ea7daf..c5be37b6 100644 --- a/icclim/tests/test_main.py +++ b/tests/test_main.py @@ -1,29 +1,26 @@ from __future__ import annotations -import os -from datetime import datetime +import contextlib +import datetime as dt +from pathlib import Path from unittest.mock import MagicMock, patch import cftime +import icclim import numpy as np import pandas as pd import pint import pytest import xarray as xr - -import icclim +from icclim import __version__ as ICCLIM_VERSION from icclim.ecad.ecad_indices import EcadIndexRegistry from icclim.generic_indices.threshold import build_threshold from icclim.icclim_exceptions import InvalidIcclimArgumentError -from icclim.models.constants import ( - ICCLIM_VERSION, - PART_OF_A_WHOLE_UNIT, - REFERENCE_PERIOD_ID, - UNITS_KEY, -) +from icclim.models.constants import PART_OF_A_WHOLE_UNIT, REFERENCE_PERIOD_ID, UNITS_KEY from icclim.models.frequency import FrequencyRegistry from icclim.models.index_group import IndexGroupRegistry -from icclim.tests.testing_utils import K2C, stub_pr, stub_tas + +from tests.testing_utils import K2C, stub_pr, stub_tas @patch("icclim.main.index") @@ -32,7 +29,8 @@ def test_deprecated_indice(log_mock: MagicMock, index_mock: MagicMock): icclim.main.log = log_mock icclim.indice() log_mock.deprecation_warning.assert_called_once_with( - old="icclim.indice", new="icclim.index" + old="icclim.indice", + new="icclim.index", ) index_mock.assert_called_once() @@ -40,7 +38,7 @@ def test_deprecated_indice(log_mock: MagicMock, index_mock: MagicMock): HEAT_INDICES = ["SU", "TR", "WSDI", "TG90p", "TN90p", "TX90p", "TXx", "TNx", "CSU"] -@pytest.mark.slow +@pytest.mark.slow() class Test_Integration: """ Integration tests. @@ -53,13 +51,13 @@ class Test_Integration: """ - OUTPUT_FILE = "out.nc" + OUTPUT_FILE = Path("out.nc") TIME_RANGE = pd.date_range(start="2042-01-01", end="2045-12-31", freq="D") CF_TIME_RANGE = xr.cftime_range("2042-01-01", end="2045-12-31", freq="D") data = xr.DataArray( data=(np.full(len(TIME_RANGE), 20).reshape((len(TIME_RANGE), 1, 1))), dims=["time", "lat", "lon"], - coords=dict(lat=[42], lon=[42], time=TIME_RANGE), + coords={"lat": [42], "lon": [42], "time": TIME_RANGE}, attrs={UNITS_KEY: "degC"}, ) full_data = data.to_dataset(name="tas") @@ -79,7 +77,7 @@ class Test_Integration: data_cf_time = xr.DataArray( data=(np.full(len(TIME_RANGE), 20).reshape((len(TIME_RANGE), 1, 1))), dims=["time", "lat", "lon"], - coords=dict(lat=[42], lon=[42], time=CF_TIME_RANGE), + coords={"lat": [42], "lon": [42], "time": CF_TIME_RANGE}, attrs={UNITS_KEY: "degC"}, ) @@ -87,33 +85,34 @@ class Test_Integration: time_bounds = xr.DataArray( data=[[t, t + np.timedelta64(1, "h")] for t in data.time.values], dims=["time", "bounds"], - coords=dict(bounds=[0, 1], time=TIME_RANGE), + coords={"bounds": [0, 1], "time": TIME_RANGE}, ).astype("object") dataset_with_time_bounds = xr.Dataset( - dict(data=data, time_bounds=time_bounds), + {"data": data, "time_bounds": time_bounds}, ) - not_spi_indices = list( - filter(lambda x: "spi" not in x.short_name.lower(), EcadIndexRegistry.values()) + not_spi_indices = tuple( + filter(lambda x: "spi" not in x.short_name.lower(), EcadIndexRegistry.values()), ) @pytest.fixture(autouse=True) - def cleanup(self): + def _cleanup(self): # setup # ... yield # teardown - try: - os.remove(self.OUTPUT_FILE) - except FileNotFoundError: - pass + with contextlib.suppress(FileNotFoundError): + self.OUTPUT_FILE.unlink() def test_index_SU(self): tas = stub_tas(tas_value=26 + K2C) tas[:5] = 0 res = icclim.index( - index_name="SU", in_files=tas, out_file=self.OUTPUT_FILE, slice_mode="ms" + index_name="SU", + in_files=tas, + out_file=self.OUTPUT_FILE, + slice_mode="ms", ) assert f"icclim version: {ICCLIM_VERSION}" in res.attrs["history"] assert res.SU.isel(time=0) == 26 # January @@ -183,16 +182,22 @@ def test_index_SU__time_selection(self): index_name="SU", in_files=self.data, out_file=self.OUTPUT_FILE, - time_range=[datetime(2042, 7, 19), datetime(2044, 8, 14)], + time_range=[ + dt.datetime(2042, 7, 19, tzinfo=dt.timezone.utc), + dt.datetime(2044, 8, 14, tzinfo=dt.timezone.utc), + ], ) # THEN - assert res_string_dates.time_bounds[0, 0] == np.datetime64(datetime(2042, 1, 1)) + assert res_string_dates.time_bounds[0, 0] == np.datetime64( + dt.datetime(2042, 1, 1, tzinfo=dt.timezone.utc), + ) assert res_string_dates.time_bounds[0, 1] == np.datetime64( - datetime(2042, 12, 31) + dt.datetime(2042, 12, 31, tzinfo=dt.timezone.utc), ) np.testing.assert_array_equal(res_string_dates.SU, res_datetime_dates.SU) np.testing.assert_array_equal( - res_string_dates.time_bounds, res_datetime_dates.time_bounds + res_string_dates.time_bounds, + res_datetime_dates.time_bounds, ) def test_index_SU__pandas_time_slice_mode(self): @@ -204,8 +209,12 @@ def test_index_SU__pandas_time_slice_mode(self): slice_mode="2W-WED", ) # THEN - assert res.time_bounds[0, 0] == np.datetime64(datetime(2042, 1, 1)) - assert res.time_bounds[0, 1] == np.datetime64(datetime(2042, 1, 14)) + assert res.time_bounds[0, 0] == np.datetime64( + dt.datetime(2042, 1, 1, tzinfo=dt.timezone.utc), + ) + assert res.time_bounds[0, 1] == np.datetime64( + dt.datetime(2042, 1, 14, tzinfo=dt.timezone.utc), + ) assert ( res.SU.attrs["standard_name"] == "number_of_days_when_maximum_air_temperature_is_greater_than_threshold" @@ -225,7 +234,8 @@ def test_index_SU__monthy_sampled(self): ) np.testing.assert_array_equal(0, res.SU) np.testing.assert_array_equal( - len(np.unique(self.TIME_RANGE.year)) * 12, len(res.time) + len(np.unique(self.TIME_RANGE.year)) * 12, + len(res.time), ) def test_index_SU__monthy_sampled_cf_time(self): @@ -237,13 +247,26 @@ def test_index_SU__monthy_sampled_cf_time(self): ) np.testing.assert_array_equal(0, res.SU) np.testing.assert_array_equal( - len(np.unique(self.TIME_RANGE.year)) * 12, len(res.time) + len(np.unique(self.TIME_RANGE.year)) * 12, + len(res.time), ) assert res.time_bounds.sel(time=res.time[0])[0] == cftime.DatetimeGregorian( - 2042, 1, 1, 0, 0, 0, 0 + 2042, + 1, + 1, + 0, + 0, + 0, + 0, ) assert res.time_bounds.sel(time=res.time[0])[1] == cftime.DatetimeGregorian( - 2042, 1, 31, 0, 0, 0, 0 + 2042, + 1, + 31, + 0, + 0, + 0, + 0, ) def test_index_SU__DJF_cf_time(self): @@ -257,13 +280,26 @@ def test_index_SU__DJF_cf_time(self): np.testing.assert_array_equal(res.SU.isel(time=1), 0) # "+ 1" because DJF sampling create a december month with nans before first year np.testing.assert_array_equal( - len(np.unique(self.TIME_RANGE.year)) + 1, len(res.time) + len(np.unique(self.TIME_RANGE.year)) + 1, + len(res.time), ) assert res.time_bounds.sel(time=res.time[0])[0] == cftime.DatetimeGregorian( - 2041, 12, 1, 0, 0, 0, 0 + 2041, + 12, + 1, + 0, + 0, + 0, + 0, ) assert res.time_bounds.sel(time=res.time[0])[1] == cftime.DatetimeGregorian( - 2042, 2, 28, 0, 0, 0, 0 + 2042, + 2, + 28, + 0, + 0, + 0, + 0, ) def test_indices__from_DataArray(self): @@ -373,10 +409,13 @@ def test_indices__snow_indices(self): ds["snd"] = self.data.copy(deep=True) ds["snd"].attrs[UNITS_KEY] = "cm" res = icclim.indices( - index_group=IndexGroupRegistry.SNOW, in_files=ds, out_file=self.OUTPUT_FILE + index_group=IndexGroupRegistry.SNOW, + in_files=ds, + out_file=self.OUTPUT_FILE, ) for i in filter( - lambda i: i.group == IndexGroupRegistry.SNOW, EcadIndexRegistry.values() + lambda i: i.group == IndexGroupRegistry.SNOW, + EcadIndexRegistry.values(), ): assert res[i.short_name] is not None @@ -466,9 +505,7 @@ def test_indices_all_ignore_error(self): ).compute() for i in EcadIndexRegistry.values(): # No variable in input to compute snow indices - if i.group == IndexGroupRegistry.SNOW: - assert res.data_vars.get(i.short_name, None) is None - elif "spi" in i.short_name.lower(): + if i.group == IndexGroupRegistry.SNOW or "spi" in i.short_name.lower(): assert res.data_vars.get(i.short_name, None) is None else: assert res[i.short_name] is not None @@ -479,7 +516,10 @@ def test_indices_all__error(self): ds["tasmin"] = self.data ds["pr"] = self.data.copy(deep=True) ds["pr"].attrs[UNITS_KEY] = "kg m-2 d-1" - with pytest.raises(Exception): + with pytest.raises( + InvalidIcclimArgumentError, + match=r"Index .* needs the following .*", + ): icclim.indices( index_group="all", in_files=ds, @@ -491,7 +531,10 @@ def test_index_TR(self): tas = stub_tas(tas_value=26 + K2C) tas[:5] = 0 res = icclim.index( - index_name="TR", in_files=tas, out_file=self.OUTPUT_FILE, slice_mode="ms" + index_name="TR", + in_files=tas, + out_file=self.OUTPUT_FILE, + slice_mode="ms", ) assert f"icclim version: {ICCLIM_VERSION}" in res.attrs["history"] assert res.TR.isel(time=0) == 26 # January @@ -528,7 +571,10 @@ def test_index_csu(self): tas = stub_tas(tas_value=26 + K2C) tas[10:40] = 0 res = icclim.index( - index_name="csu", in_files=tas, out_file=self.OUTPUT_FILE, slice_mode="ms" + index_name="csu", + in_files=tas, + out_file=self.OUTPUT_FILE, + slice_mode="ms", ).load() # in January there are only 10 days above 25degC assert res.CSU.isel(time=0) == 10 @@ -541,7 +587,10 @@ def test_index_gd4(self): tas = stub_tas(tas_value=26 + K2C) tas[5:15] = 0 res = icclim.index( - index_name="gd4", in_files=tas, out_file=self.OUTPUT_FILE, slice_mode="ms" + index_name="gd4", + in_files=tas, + out_file=self.OUTPUT_FILE, + slice_mode="ms", ) expected = (26 - 4) * 21 assert ( @@ -552,7 +601,9 @@ def test_index_cfd(self): tas = stub_tas(tas_value=26 + K2C) tas[5:15] = 270 # ~ -3degC res = icclim.cfd( - in_files=tas, out_file=self.OUTPUT_FILE, slice_mode="ms" + in_files=tas, + out_file=self.OUTPUT_FILE, + slice_mode="ms", ).load() # 10 days in January that are below or equal to 0degC assert res.CFD.isel(time=0) == 10 @@ -562,7 +613,10 @@ def test_index_fd(self): tas[5:15] = 0 tas[20:25] = 0 res = icclim.index( - index_name="fd", in_files=tas, out_file=self.OUTPUT_FILE, slice_mode="ms" + index_name="fd", + in_files=tas, + out_file=self.OUTPUT_FILE, + slice_mode="ms", ) assert res.FD.isel(time=0) == 15 @@ -570,7 +624,10 @@ def test_index_hd17(self): tas = stub_tas(tas_value=27 + K2C) tas[5:10] = 0 res = icclim.index( - index_name="hd17", in_files=tas, out_file=self.OUTPUT_FILE, slice_mode="ms" + index_name="hd17", + in_files=tas, + out_file=self.OUTPUT_FILE, + slice_mode="ms", ) assert res.HD17.isel(time=0) == 5 * (17 + K2C) @@ -672,10 +729,10 @@ def test_count_occurrences__date_event(self): assert "event_date_start" in res.coords assert "event_date_end" in res.coords assert res.count_occurrences.isel(time=0).event_date_end == np.datetime64( - "2042-01-11" + "2042-01-11", ) assert res.count_occurrences.isel(time=0).event_date_start == np.datetime64( - "2042-01-11" + "2042-01-11", ) def test_count_occurrences__to_percent(self): @@ -732,7 +789,9 @@ def test_count_occurrences__multiple_period_per_thresholds(self): var_name=["tmin"], index_name="count_occurrences", threshold=build_threshold( - value=[10, 99.95], operator=">=", unit="period_per" + value=[10, 99.95], + operator=">=", + unit="period_per", ), slice_mode="month", save_thresholds=True, @@ -816,7 +875,8 @@ def test_std(self): def test_slice_mode__between_date(self): time_range = xr.DataArray( - pd.date_range("2000", periods=365, freq="D"), dims=["time"] + pd.date_range("2000", periods=365, freq="D"), + dims=["time"], ) precipitation = xr.DataArray( np.ones(365), @@ -826,7 +886,8 @@ def test_slice_mode__between_date(self): ) precipitation[0:5] = [0.1, 0.1, 0.1, 2, 3] cdd = icclim.cdd( - in_files=precipitation, slice_mode=["season", ["01-02", "01-05"]] + in_files=precipitation, + slice_mode=["season", ["01-02", "01-05"]], ).CDD # The 01-01 value is ignored because we clip the wanted season before computing # the index @@ -834,7 +895,8 @@ def test_slice_mode__between_date(self): def test_rr_with_slice_mode__week(self): time_range = xr.DataArray( - pd.date_range("2000", periods=365, freq="D"), dims=["time"] + pd.date_range("2000", periods=365, freq="D"), + dims=["time"], ) precipitation = xr.DataArray( np.zeros(365), @@ -851,7 +913,8 @@ def test_rr_with_slice_mode__week(self): def test_rr_with_slice_mode__4_weeks(self): time_range = xr.DataArray( - pd.date_range("2000", periods=365, freq="D"), dims=["time"] + pd.date_range("2000", periods=365, freq="D"), + dims=["time"], ) precipitation = xr.DataArray( np.zeros(365), @@ -869,7 +932,8 @@ def test_rr_with_slice_mode__4_weeks(self): def test_mm_to_mmday(self): # GIVEN time_range = xr.DataArray( - pd.date_range("2000", periods=365, freq="D"), dims=["time"] + pd.date_range("2000", periods=365, freq="D"), + dims=["time"], ) precip = xr.DataArray( np.ones(365), @@ -886,7 +950,8 @@ def test_mm_to_mmday(self): def test_mm_to_mmday__error_bas_standard_name(self): # GIVEN time_range = xr.DataArray( - pd.date_range("2000", periods=365, freq="D"), dims=["time"] + pd.date_range("2000", periods=365, freq="D"), + dims=["time"], ) precip = xr.DataArray( np.ones(365), @@ -903,7 +968,8 @@ def test_mm_to_mmday__error_bas_standard_name(self): def test_ddnorth(self): # GIVEN time_range = xr.DataArray( - pd.date_range("2000", periods=365, freq="D"), dims=["time"] + pd.date_range("2000", periods=365, freq="D"), + dims=["time"], ) dd = xr.DataArray( np.full(365, 300), @@ -922,7 +988,8 @@ def test_ddnorth(self): def test_ddeast(self): # GIVEN time_range = xr.DataArray( - pd.date_range("2000", periods=365, freq="D"), dims=["time"] + pd.date_range("2000", periods=365, freq="D"), + dims=["time"], ) dd = xr.DataArray( np.full(365, 300), diff --git a/icclim/tests/test_rechunk.py b/tests/test_rechunk.py similarity index 60% rename from icclim/tests/test_rechunk.py rename to tests/test_rechunk.py index 59aa5b77..36e7efa6 100644 --- a/icclim/tests/test_rechunk.py +++ b/tests/test_rechunk.py @@ -6,7 +6,6 @@ import pandas as pd import pytest import xarray as xr - from icclim import create_optimized_zarr_store from icclim.icclim_exceptions import InvalidIcclimArgumentError from icclim.models.constants import UNITS_KEY @@ -17,16 +16,16 @@ def test_create_optimized_zarr_store_success(): { "tas": xr.DataArray( data=np.full(10, 42).reshape((10, 1, 1)), - coords=dict( - lat=[42], - lon=[42], - time=pd.date_range("2042-01-01", periods=10, freq="D"), - ), + coords={ + "lat": [42], + "lon": [42], + "time": pd.date_range("2042-01-01", periods=10, freq="D"), + }, dims=["time", "lat", "lon"], name="pr", attrs={UNITS_KEY: "kg m-2 d-1"}, - ) - } + ), + }, ).chunk({"time": 2}) with create_optimized_zarr_store( in_files=ds, @@ -43,26 +42,24 @@ def test_create_optimized_zarr_store_error(): { "tas": xr.DataArray( data=np.full(10, 42).reshape((10, 1, 1)), - coords=dict( - lat=[42], - lon=[42], - time=pd.date_range("2042-01-01", periods=10, freq="D"), - ), + coords={ + "lat": [42], + "lon": [42], + "time": pd.date_range("2042-01-01", periods=10, freq="D"), + }, dims=["time", "lat", "lon"], name="pr", attrs={UNITS_KEY: "kg m-2 d-1"}, - ) - } + ), + }, ).chunk({"time": 2}) # Then - with pytest.raises(InvalidIcclimArgumentError): - # When - with create_optimized_zarr_store( - in_files=ds, - var_names="TATAYOYO!", - target_zarr_store_name="yolo.zarr", - ): - pass + with pytest.raises(InvalidIcclimArgumentError), create_optimized_zarr_store( + in_files=ds, + var_names="TATAYOYO!", + target_zarr_store_name="yolo.zarr", + ): + pass @patch("rechunker.rechunk") @@ -71,24 +68,23 @@ def test_create_optimized_zarr_store_no_rechunk(rechunk_mock: MagicMock): { "tas": xr.DataArray( data=np.full(10, 42).reshape((10, 1, 1)), - coords=dict( - lat=[42], - lon=[42], - time=pd.date_range("2042-01-01", periods=10, freq="D"), - ), + coords={ + "lat": [42], + "lon": [42], + "time": pd.date_range("2042-01-01", periods=10, freq="D"), + }, dims=["time", "lat", "lon"], name="pr", attrs={UNITS_KEY: "kg m-2 d-1"}, - ) - } + ), + }, ).chunk({"time": 2}) # When - with pytest.raises(InvalidIcclimArgumentError): - with create_optimized_zarr_store( - in_files=ds, - var_names="tas", - target_zarr_store_name="n/a", - chunking={"time": 2}, - ): - pass + with pytest.raises(InvalidIcclimArgumentError), create_optimized_zarr_store( + in_files=ds, + var_names="tas", + target_zarr_store_name="n/a", + chunking={"time": 2}, + ): + pass rechunk_mock.assert_not_called() diff --git a/icclim/tests/test_threshold.py b/tests/test_threshold.py similarity index 92% rename from icclim/tests/test_threshold.py rename to tests/test_threshold.py index 23b7f99e..aee69f08 100644 --- a/icclim/tests/test_threshold.py +++ b/tests/test_threshold.py @@ -1,6 +1,7 @@ from __future__ import annotations -import os +import contextlib +from pathlib import Path from typing import Callable import numpy as np @@ -8,9 +9,6 @@ import pint import pytest import xarray as xr -from xclim.core.calendar import percentile_doy -from xclim.core.units import units as xc_units - from icclim.generic_indices.threshold import ( BasicThreshold, BoundedThreshold, @@ -22,6 +20,8 @@ from icclim.models.logical_link import LogicalLinkRegistry from icclim.models.operator import OperatorRegistry from icclim.pre_processing.input_parsing import PercentileDataArray +from xclim.core.calendar import percentile_doy +from xclim.core.units import units as xc_units def test_value_error(): @@ -115,7 +115,8 @@ def test_build_bounded_threshold__from_args(): t1 = build_threshold(">10degC") t2 = build_threshold(">12 doy_per") t3 = build_threshold( - thresholds=(t1, t2), logical_link=LogicalLinkRegistry.LOGICAL_OR + thresholds=(t1, t2), + logical_link=LogicalLinkRegistry.LOGICAL_OR, ) assert isinstance(t3, BoundedThreshold) assert isinstance(t3.left_threshold, BasicThreshold) @@ -163,7 +164,7 @@ def test_per_threshold_min_value__operand_error(): def test_per_threshold_min_value__type_error(): with pytest.raises(NotImplementedError): - build_threshold(">10doy_per", threshold_min_value=dict(random="stuff")) # noqa + build_threshold(">10doy_per", threshold_min_value={"random": "stuff"}) def test_per_threshold_min_value__string(): @@ -202,7 +203,7 @@ def test_build_per_threshold__from_query(): assert res.is_ready is False assert isinstance(res.prepare, Callable) with pytest.raises(RuntimeError): # not computed yet - res.value # noqa + _ = res.value def test_build_basic_threshold__from_dataarray(): @@ -210,7 +211,7 @@ def test_build_basic_threshold__from_dataarray(): data = xr.DataArray( data=(np.full(len(TIME_RANGE), 20).reshape((len(TIME_RANGE), 1, 1))), dims=["time", "lat", "lon"], - coords=dict(lat=[42], lon=[42], time=TIME_RANGE), + coords={"lat": [42], "lon": [42], "time": TIME_RANGE}, attrs={UNITS_KEY: "degC"}, name="toto", ) @@ -227,7 +228,7 @@ def test_build_basic_threshold__from_dataset(): ds = xr.DataArray( data=(np.full(len(TIME_RANGE), 20).reshape((len(TIME_RANGE), 1, 1))), dims=["time", "lat", "lon"], - coords=dict(lat=[42], lon=[42], time=TIME_RANGE), + coords={"lat": [42], "lon": [42], "time": TIME_RANGE}, attrs={UNITS_KEY: "degC"}, name="tas", ).to_dataset() @@ -245,7 +246,7 @@ def test_build_basic_threshold__from_dataset__error(): ds = xr.DataArray( data=(np.full(len(TIME_RANGE), 20).reshape((len(TIME_RANGE), 1, 1))), dims=["time", "lat", "lon"], - coords=dict(lat=[42], lon=[42], time=TIME_RANGE), + coords={"lat": [42], "lon": [42], "time": TIME_RANGE}, attrs={UNITS_KEY: "degC"}, name="toto", ).to_dataset() @@ -256,31 +257,29 @@ def test_build_basic_threshold__from_dataset__error(): class Test_FileBased: - IN_FILE_PATH = "in.nc" + IN_FILE_PATH = Path("in.nc") TIME_RANGE = pd.date_range(start="2042-01-01", end="2045-12-31", freq="D") data = xr.DataArray( data=(np.full(len(TIME_RANGE), 20).reshape((len(TIME_RANGE), 1, 1))), dims=["time", "lat", "lon"], - coords=dict(lat=[42], lon=[42], time=TIME_RANGE), + coords={"lat": [42], "lon": [42], "time": TIME_RANGE}, attrs={UNITS_KEY: "degC"}, name="toto", ) @pytest.fixture(autouse=True) - def cleanup(self): + def _cleanup(self): # setup # ... yield # teardown - try: - os.remove(self.IN_FILE_PATH) - except FileNotFoundError: - pass + with contextlib.suppress(FileNotFoundError): + self.IN_FILE_PATH.unlink() def test_build_basic_threshold__from_file(self): self.data.to_netcdf(path=self.IN_FILE_PATH) - res = build_threshold(operator=">=", value=self.IN_FILE_PATH) + res = build_threshold(operator=">=", value=str(self.IN_FILE_PATH)) assert isinstance(res, BasicThreshold) assert res.operator == OperatorRegistry.GREATER_OR_EQUAL xr.testing.assert_equal(res.value, self.data) @@ -290,7 +289,9 @@ def test_build_basic_threshold__from_file(self): def test_threshold_min_value__number_from_file(self): self.data.to_netcdf(path=self.IN_FILE_PATH) res = build_threshold( - operator=">=", value=self.IN_FILE_PATH, threshold_min_value=5 + operator=">=", + value=str(self.IN_FILE_PATH), + threshold_min_value=5, ) assert res.threshold_min_value == xc_units.Quantity(5, "degC") @@ -298,7 +299,7 @@ def test_build_percentile_threshold__from_file(self): doys = percentile_doy(self.data) doys = PercentileDataArray.from_da(doys) doys.to_netcdf(path=self.IN_FILE_PATH) - res = build_threshold(operator=">=", value=self.IN_FILE_PATH) + res = build_threshold(operator=">=", value=str(self.IN_FILE_PATH)) assert isinstance(res, PercentileThreshold) assert res.operator == OperatorRegistry.GREATER_OR_EQUAL xr.testing.assert_equal(res.value, doys) diff --git a/icclim/tests/test_user_index.py b/tests/test_user_index.py similarity index 70% rename from icclim/tests/test_user_index.py rename to tests/test_user_index.py index a530b3d6..89958574 100644 --- a/icclim/tests/test_user_index.py +++ b/tests/test_user_index.py @@ -1,7 +1,5 @@ from __future__ import annotations -from xclim.core.calendar import build_climatology_bounds - import icclim from icclim.models.constants import ( UNITS_KEY, @@ -9,7 +7,9 @@ USER_INDEX_TEMPERATURE_STAMP, ) from icclim.models.operator import OperatorRegistry -from icclim.tests.testing_utils import stub_tas +from xclim.core.calendar import build_climatology_bounds + +from tests.testing_utils import stub_tas class Test_max: @@ -19,9 +19,12 @@ def test_simple(self): # WHEN result = icclim.index( in_files=da, - user_index=dict( - index_name="data", calc_operation="max", coef=1, logical_operation=None - ), + user_index={ + "index_name": "data", + "calc_operation": "max", + "coef": 1, + "logical_operation": None, + }, ) # THEN assert result.data[0] == 20 @@ -34,7 +37,7 @@ def test_simple(self): # WHEN result = icclim.index( in_files=da, - user_index=dict(index_name="data", calc_operation="min"), + user_index={"index_name": "data", "calc_operation": "min"}, ) # THEN assert result.data[0] == -20 @@ -47,7 +50,7 @@ def test_simple(self): # WHEN result = icclim.index( in_files=da, - user_index=dict(index_name="data", calc_operation="mean"), + user_index={"index_name": "data", "calc_operation": "mean"}, ) # THEN assert result.data[0] == 2 @@ -59,7 +62,7 @@ def test_simple(self): # WHEN result = icclim.index( in_files=da, - user_index=dict(index_name="data", calc_operation="sum"), + user_index={"index_name": "data", "calc_operation": "sum"}, slice_mode="year", ) # THEN @@ -75,12 +78,12 @@ def test_simple(self): # WHEN result = icclim.index( in_files=da, - user_index=dict( - index_name="data", - calc_operation="nb_events", - thresh=15, - logical_operation=OperatorRegistry.GREATER, - ), + user_index={ + "index_name": "data", + "calc_operation": "nb_events", + "thresh": 15, + "logical_operation": OperatorRegistry.GREATER, + }, slice_mode="month", ) # THEN @@ -94,12 +97,12 @@ def test_simple_default_percentile(self): # WHEN result = icclim.index( in_files=da, - user_index=dict( - index_name="data", - calc_operation="nb_events", - thresh="50p", - logical_operation=OperatorRegistry.GREATER, - ), + user_index={ + "index_name": "data", + "calc_operation": "nb_events", + "thresh": "50p", + "logical_operation": OperatorRegistry.GREATER, + }, base_period_time_range=build_climatology_bounds(da), slice_mode="month", ) @@ -114,13 +117,13 @@ def test_simple_period_percentile(self): # WHEN result = icclim.index( in_files=da, - user_index=dict( - index_name="data", - calc_operation="nb_events", - thresh="50p", - var_type=USER_INDEX_PRECIPITATION_STAMP, - logical_operation=OperatorRegistry.GREATER, - ), + user_index={ + "index_name": "data", + "calc_operation": "nb_events", + "thresh": "50p", + "var_type": USER_INDEX_PRECIPITATION_STAMP, + "logical_operation": OperatorRegistry.GREATER, + }, base_period_time_range=build_climatology_bounds(da), slice_mode="month", ) @@ -135,13 +138,13 @@ def test_simple_doy_percentile(self): # WHEN result = icclim.index( in_files=da, - user_index=dict( - index_name="data", - calc_operation="nb_events", - thresh="80p", - var_type=USER_INDEX_TEMPERATURE_STAMP, - logical_operation=OperatorRegistry.GREATER, - ), + user_index={ + "index_name": "data", + "calc_operation": "nb_events", + "thresh": "80p", + "var_type": USER_INDEX_TEMPERATURE_STAMP, + "logical_operation": OperatorRegistry.GREATER, + }, base_period_time_range=build_climatology_bounds(da), slice_mode="month", ) @@ -157,13 +160,13 @@ def test_multi_threshold_or(self): result = icclim.index( in_files={"tmax": tmax, "tmin": tmin}, index_name="data", - user_index=dict( - calc_operation="nb_events", - thresh=[12, -20], - var_type=USER_INDEX_TEMPERATURE_STAMP, - logical_operation=[OperatorRegistry.GREATER, OperatorRegistry.EQUAL], - link_logical_operations="or", - ), + user_index={ + "calc_operation": "nb_events", + "thresh": [12, -20], + "var_type": USER_INDEX_TEMPERATURE_STAMP, + "logical_operation": [OperatorRegistry.GREATER, OperatorRegistry.EQUAL], + "link_logical_operations": "or", + }, slice_mode="month", ) # THEN @@ -179,13 +182,13 @@ def test_multi_threshold_and(self): result = icclim.index( in_files={"tmax": tmax, "tmin": tmin}, index_name="data", - user_index=dict( - calc_operation="nb_events", - thresh=[12, -20], - var_type=USER_INDEX_TEMPERATURE_STAMP, - logical_operation=[OperatorRegistry.GREATER, OperatorRegistry.EQUAL], - link_logical_operations="and", - ), + user_index={ + "calc_operation": "nb_events", + "thresh": [12, -20], + "var_type": USER_INDEX_TEMPERATURE_STAMP, + "logical_operation": [OperatorRegistry.GREATER, OperatorRegistry.EQUAL], + "link_logical_operations": "and", + }, slice_mode="month", ) # THEN @@ -205,11 +208,11 @@ def test_run_mean_min(self): result = icclim.index( in_files={"tmax": tmax}, index_name="data", - user_index=dict( - calc_operation="run_mean", - extreme_mode="min", - window_width=5, - ), + user_index={ + "calc_operation": "run_mean", + "extreme_mode": "min", + "window_width": 5, + }, slice_mode="month", ) # THEN @@ -226,10 +229,10 @@ def test_run_mean_max(self): in_files={"tmax": tmax}, index_name="data", rolling_window_width=2, - user_index=dict( - calc_operation="run_mean", - extreme_mode="max", - ), + user_index={ + "calc_operation": "run_mean", + "extreme_mode": "max", + }, slice_mode="month", ) # THEN @@ -252,10 +255,10 @@ def test_run_sum_min(self): in_files={"tmax": tmax}, index_name="data", rolling_window_width=5, - user_index=dict( - calc_operation="run_sum", - extreme_mode="min", - ), + user_index={ + "calc_operation": "run_sum", + "extreme_mode": "min", + }, slice_mode="month", ) # THEN @@ -272,10 +275,10 @@ def test_run_sum_max(self): in_files={"tmax": tmax}, index_name="data", rolling_window_width=2, - user_index=dict( - calc_operation="run_sum", - extreme_mode="max", - ), + user_index={ + "calc_operation": "run_sum", + "extreme_mode": "max", + }, slice_mode="month", ) # THEN @@ -293,11 +296,11 @@ def test_simple(self): result = icclim.index( in_files={"tmax": tmax}, index_name="data", - user_index=dict( - calc_operation="max_nb_consecutive_events", - thresh=10.0, - logical_operation=OperatorRegistry.EQUAL, - ), + user_index={ + "calc_operation": "max_nb_consecutive_events", + "thresh": 10.0, + "logical_operation": OperatorRegistry.EQUAL, + }, slice_mode="year", ) # THEN @@ -314,9 +317,9 @@ def test_simple(self): result = icclim.index( in_files={"tmax2": tmax2, "tmax": tmax}, index_name="data", - user_index=dict( - calc_operation="anomaly", - ), + user_index={ + "calc_operation": "anomaly", + }, slice_mode="year", ) # THEN @@ -339,9 +342,9 @@ def test_single_var(self): base_period_time_range=ref_bds, index_name="data", sampling_method="groupby", - user_index=dict( - calc_operation="anomaly", - ), + user_index={ + "calc_operation": "anomaly", + }, slice_mode="month", ) # THEN @@ -358,7 +361,7 @@ def test_simple_percent(self): index_name="data", in_files={"tmax2": tmax2, "tmax": tmax}, out_unit="%", - user_index=dict(calc_operation="anomaly"), + user_index={"calc_operation": "anomaly"}, ) # THEN assert (result.data == 10).all() diff --git a/icclim/tests/testing_utils.py b/tests/testing_utils.py similarity index 89% rename from icclim/tests/testing_utils.py rename to tests/testing_utils.py index c91ed6bd..6cb34b0c 100644 --- a/icclim/tests/testing_utils.py +++ b/tests/testing_utils.py @@ -3,15 +3,14 @@ import numpy as np import pandas as pd import xarray as xr - from icclim.models.constants import UNITS_KEY VALUE_COUNT = 365 * 5 + 1 # 5 years of data (with 1 leap year) -COORDS = dict( - lat=[42], - lon=[42], - time=pd.date_range("2042-01-01", periods=VALUE_COUNT, freq="D"), -) +COORDS = { + "lat": [42], + "lon": [42], + "time": pd.date_range("2042-01-01", periods=VALUE_COUNT, freq="D"), +} K2C = 273.15 CF_TIME_RANGE = xr.cftime_range("2042-01-01", periods=VALUE_COUNT, freq="D") diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 00000000..81bd45f6 --- /dev/null +++ b/tools/__init__.py @@ -0,0 +1 @@ +"""icclim scripts to manage the library.""" diff --git a/tools/build.sh b/tools/build.sh index 07dfbb2d..bc1b45a9 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -1,7 +1,7 @@ # Run it with `./tools/build.sh` # from icclim root # and with "icclim-dev" conda environment activated -python -m setup install -python ./tools/extract-icclim-funs.py ./icclim/_generated_api.py -git add ./icclim/_generated_api.py +pip install . +python ./tools/extract-icclim-funs.py ./src/icclim/_generated_api.py +git add ./src/icclim/_generated_api.py pre-commit run diff --git a/tools/extract-icclim-funs.py b/tools/extract_icclim_funs.py similarity index 68% rename from tools/extract-icclim-funs.py rename to tools/extract_icclim_funs.py index 34b49b29..82db176d 100644 --- a/tools/extract-icclim-funs.py +++ b/tools/extract_icclim_funs.py @@ -1,5 +1,6 @@ """ Icclim indices extractor. + It creates a new python module which wraps each icclim index as a function. Each generated functions signature is consistent with icclim.index signature but, all the unused parameters are trimmed from the signature. @@ -12,6 +13,7 @@ python -m setup install Then the script can be run with: + .. code-block:: console python ./tools/extract-icclim-funs.py @@ -21,10 +23,10 @@ import copy import inspect -import os import re import sys from pathlib import Path +from typing import TYPE_CHECKING import icclim from icclim.ecad.ecad_indices import EcadIndexRegistry @@ -42,9 +44,12 @@ from icclim.models.frequency import Frequency from icclim.models.netcdf_version import NetcdfVersion from icclim.models.quantile_interpolation import QuantileInterpolation -from icclim.models.standard_index import StandardIndex from icclim.models.user_index_dict import UserIndexDict +if TYPE_CHECKING: + from icclim.models.standard_index import StandardIndex + + QUANTILE_INDEX_FIELDS = [ "base_period_time_range", "only_leap_years", @@ -66,40 +71,43 @@ This function has been auto-generated. """ -DEFAULT_OUTPUT_PATH = Path(os.path.dirname(os.path.abspath(__file__))) / "pouet.py" +DEFAULT_OUTPUT_PATH = Path(__file__).parent / "pouet.py" PATH_TO_ECAD_DOC_FILE = ( - Path(os.path.dirname(os.path.abspath(__file__))) - / "../doc/source/references" - / "ecad_functions_api.rst" + Path(__file__).parent / "../doc/source/references" / "ecad_functions_api.rst" ) PATH_TO_GENERIC_DOC_FILE = ( - Path(os.path.dirname(os.path.abspath(__file__))) - / "../doc/source/references" - / "generic_functions_api.rst" + Path(__file__).parent / "../doc/source/references" / "generic_functions_api.rst" ) DOC_START_PLACEHOLDER = ".. Generated API comment:Begin\n" DOC_END_PLACEHOLDER = f"{TAB}{TAB}.. Generated API comment:End" -MODULE_HEADER = f'''""" +MODULE_HEADER = f''' +# ruff: noqa: A001, E501 +""" +icclim's API for ECAD indices and generic indices. + This module has been auto-generated. To modify these, edit the extractor tool in `tools/extract-icclim-funs.py`. This module exposes each climate index as individual functions for convenience. """ -# flake8: noqa E501 from __future__ import annotations -from datetime import datetime -from typing import Sequence - -from xarray.core.dataset import Dataset +from typing import TYPE_CHECKING import icclim from {Threshold.__module__} import {Threshold.__name__}, {build_threshold.__name__} -from {Verbosity.__module__} import {Verbosity.__name__} -from icclim.icclim_types import InFileLike, SamplingMethodLike -from {Frequency.__module__} import {Frequency.__name__}, FrequencyLike -from {NetcdfVersion.__module__} import {NetcdfVersion.__name__} -from {QuantileInterpolation.__module__} import {QuantileInterpolation.__name__} -from {UserIndexDict.__module__} import {UserIndexDict.__name__} + +if TYPE_CHECKING: + import datetime as dt + from collections.abc import Sequence + + from xarray.core.dataset import Dataset + + from {Verbosity.__module__} import {Verbosity.__name__} + from icclim.icclim_types import FrequencyLike, InFileLike, SamplingMethodLike + from {Frequency.__module__} import {Frequency.__name__} + from {NetcdfVersion.__module__} import {NetcdfVersion.__name__} + from {QuantileInterpolation.__module__} import {QuantileInterpolation.__name__} + from {UserIndexDict.__module__} import {UserIndexDict.__name__} ''' @@ -146,24 +154,24 @@ ) -def generate_api(path): +def _generate_api(path: Path) -> None: ecad_indices = EcadIndexRegistry.values() generic_indices = GenericIndicatorRegistry.values() - with open(path, "w") as f: + with Path.open(path, "w") as f: acc = MODULE_HEADER acc += "__all__ = [\n" - ecad_index_names = list(map(lambda x: x.short_name, ecad_indices)) - generic_index_names = list(map(lambda x: x.name, generic_indices)) + ecad_index_names = [x.short_name for x in ecad_indices] + generic_index_names = [x.name for x in generic_indices] names = generic_index_names + ecad_index_names + ["custom_index"] - formatted_names = map(lambda x: f'{TAB}"{x.lower()}"', names) + formatted_names = (f'{TAB}"{x.lower()}"' for x in names) acc += ",\n".join(formatted_names) acc += ",\n]\n\n" standard_indices = [ - get_standard_index_declaration(index) for index in ecad_indices + _get_standard_index_declaration(index) for index in ecad_indices ] - custom_index = [get_user_index_declaration()] + custom_index = [_get_user_index_declaration()] generic_indices = [ - get_generic_index_declaration(generic_index) + _get_generic_index_declaration(generic_index) for generic_index in generic_indices ] indices_to_write = generic_indices + standard_indices + custom_index @@ -171,29 +179,31 @@ def generate_api(path): f.write(acc) -def get_user_index_declaration() -> str: +def _get_user_index_declaration() -> str: icclim_index_args = dict(inspect.signature(icclim.index).parameters) pop_args = DEPRECATED_ARGS + UNNECESSARY_ARGS # User indices have their own way of writing thresholds pop_args.append("threshold") for pop_arg in pop_args: icclim_index_args.pop(pop_arg) - fun_signature_args = build_fun_signature_args(icclim_index_args) - args_docs = get_params_docstring( - list(icclim_index_args.keys()), icclim.index.__doc__ + fun_signature_args = _build_fun_signature_args(icclim_index_args) + args_docs = _get_params_docstring( + list(icclim_index_args.keys()), + icclim.index.__doc__, ) - common_args = map(lambda arg: f"{arg}={arg}", icclim_index_args) + common_args = (f"{arg}={arg}" for arg in icclim_index_args) formatted_common_args = f",\n{TAB}{TAB}".join(common_args) return f""" def custom_index( user_index: UserIndexDict, {fun_signature_args}, ) -> Dataset: - \"\"\" - This function can be used to create indices using simple operators. - Use the `user_index` parameter to describe how the index should be computed. - You can find some examples in icclim documentation at :ref:`custom_indices` - {args_docs} + \"\"\"Compute custom indices using simple operators. + + Use the `user_index` parameter to describe how the index should be computed. + You can find some examples in icclim documentation at :ref:`custom_indices` + + {args_docs} {END_NOTE} \"\"\" return icclim.index( @@ -203,11 +213,11 @@ def custom_index( """ -def build_fun_signature_args(args: dict) -> str: - return f",\n{TAB}".join(map(get_parameter_declaration, args.values())) +def _build_fun_signature_args(args: dict) -> str: + return f",\n{TAB}".join(map(_get_parameter_declaration, args.values())) -def get_generic_index_declaration(index: GenericIndicator) -> str: +def _get_generic_index_declaration(index: GenericIndicator) -> str: pop_args = copy.copy(GENERIC_POP_ARGS) if index is not GenericIndicatorRegistry.SumOfSpellLengths: pop_args += ["min_spell_length"] @@ -221,15 +231,16 @@ def get_generic_index_declaration(index: GenericIndicator) -> str: ]: pop_args += ["rolling_window_width"] index_args = _get_arguments(pop_args) - fun_signature_args = build_fun_signature_args(index_args) - args_docs = get_params_docstring(list(index_args.keys()), icclim.index.__doc__) - args = map(lambda arg: f"{arg}={arg}", index_args) + fun_signature_args = _build_fun_signature_args(index_args) + args_docs = _get_params_docstring(list(index_args.keys()), icclim.index.__doc__) + args = (f"{arg}={arg}" for arg in index_args) formatted_args = f",\n{TAB}{TAB}".join(list(args)) return f""" def {index.name.lower()}( {fun_signature_args}, ) -> Dataset: - \"\"\" + \"\"\"{index.name} + {index.definition} {args_docs} @@ -242,19 +253,19 @@ def {index.name.lower()}( """ -def get_standard_index_declaration(index: StandardIndex) -> str: +def _get_standard_index_declaration(index: StandardIndex) -> str: if _is_quantile_based(index): index_args = _get_arguments(ECAD_POP_ARGS) elif _can_have_reference_period(index): index_args = _get_arguments(ECAD_POP_ARGS + NON_REFERENCE_FIELDS) else: index_args = _get_arguments(ECAD_POP_ARGS + QUANTILE_INDEX_FIELDS) - fun_signature_args = build_fun_signature_args(index_args) - args_docs = get_params_docstring(list(index_args.keys()), icclim.index.__doc__) - common_args = map(lambda arg: f"{arg}={arg}", index_args) + fun_signature_args = _build_fun_signature_args(index_args) + args_docs = _get_params_docstring(list(index_args.keys()), icclim.index.__doc__) + common_args = (f"{arg}={arg}" for arg in index_args) args = list(common_args) - thresh_arg = get_threshold_argument(index) - output_unit_arg = get_output_unit_argument(index) + thresh_arg = _get_threshold_argument(index) + output_unit_arg = _get_output_unit_argument(index) if thresh_arg: args += [thresh_arg] if output_unit_arg: @@ -264,8 +275,9 @@ def get_standard_index_declaration(index: StandardIndex) -> str: def {index.short_name.lower()}( {fun_signature_args}, ) -> Dataset: - \"\"\" - {index.short_name}: {index.definition} + \"\"\"{index.short_name} + + {index.definition} Source: {index.source}. {args_docs} @@ -293,26 +305,25 @@ def _get_arguments(pop_args: list[str]) -> dict[str, inspect.Parameter]: return icclim_index_args -def get_output_unit_argument(index: StandardIndex) -> str: +def _get_output_unit_argument(index: StandardIndex) -> str: if index.output_unit is not None: return f'out_unit="{index.output_unit}"' return "" -def get_threshold_argument(index: StandardIndex) -> str: +def _get_threshold_argument(index: StandardIndex) -> str: if isinstance(index.threshold, (str, Threshold)): - return f"threshold={format_thresh(index.threshold)}" - elif isinstance(index.threshold, (list, tuple)): - result = f"threshold=[" + return f"threshold={_format_thresh(index.threshold)}" + if isinstance(index.threshold, (list, tuple)): + result = "threshold=[" for t in index.threshold: - result += format_thresh(t) + "," + result += _format_thresh(t) + "," result += "]" return result - else: - return "" + return "" -def get_parameter_declaration(param: inspect.Parameter) -> str: +def _get_parameter_declaration(param: inspect.Parameter) -> str: annotation = param.annotation if type(annotation) is type: annotation = annotation.__name__ @@ -322,18 +333,18 @@ def get_parameter_declaration(param: inspect.Parameter) -> str: if param.default is inspect.Parameter.empty: return prefix default = param.default - if type(default) is str: + if isinstance(default, str): default = f'"{default.__str__()}"' return f"{prefix} = {default}" -def get_params_docstring(args: list[str], index_docstring: str) -> str: - result = f"Parameters\n{TAB}----------\n" +def _get_params_docstring(args: list[str], index_docstring: str) -> str: + result = f"Parameters\n{TAB}----------" # regex to find `\n toto: str` or similar declaration of argument regex = re.compile(r"\n\s{4}\w+.*: .*") args_declaration = list(regex.finditer(index_docstring)) for arg in args: - for i in range(0, len(args_declaration) - 2): + for i in range(len(args_declaration) - 2): # `-2` because we have specific handler for the last argument if args_declaration[i].group().strip().startswith(arg): result += index_docstring[ @@ -345,7 +356,7 @@ def get_params_docstring(args: list[str], index_docstring: str) -> str: return result -def format_thresh(t: str | Threshold) -> str: +def _format_thresh(t: str | Threshold) -> str: params = {} if isinstance(t, str): t = build_threshold(t) @@ -364,32 +375,32 @@ def format_thresh(t: str | Threshold) -> str: return acc -def generate_doc(doc_path, replacing_content): - with open(doc_path) as f: +def _generate_doc(doc_path: Path, replacing_content: str) -> None: + with Path.open(doc_path) as f: content = "".join(f.readlines()) replace_start_index = ( content.find(DOC_START_PLACEHOLDER) + len(DOC_START_PLACEHOLDER) + 1 ) replace_end_index = content.find(DOC_END_PLACEHOLDER) - with open(doc_path, "w") as f: + with Path.open(doc_path, "w") as f: replaced_content = content[replace_start_index:replace_end_index] res = content.replace(replaced_content, replacing_content) f.write(res) -def get_ecad_doc() -> str: - names = map(lambda x: x.short_name, EcadIndexRegistry.values()) - names = list(names) + ["custom_index"] - formatted_names = map(lambda x: f"{TAB}{TAB}{x.lower()}", names) +def _get_ecad_doc() -> str: + names = (x.short_name for x in EcadIndexRegistry.values()) + names = [*list(names), "custom_index"] + formatted_names = (f"{TAB}{TAB}{x.lower()}" for x in names) replacing_content = "" replacing_content += "\n".join(formatted_names) replacing_content += "\n\n" return replacing_content -def get_generic_doc() -> str: - names = map(lambda x: x.name, GenericIndicatorRegistry.values()) - formatted_names = map(lambda x: f"{TAB}{TAB}{x.lower()}", names) +def _get_generic_doc() -> str: + names = (x.name for x in GenericIndicatorRegistry.values()) + formatted_names = (f"{TAB}{TAB}{x.lower()}" for x in names) replacing_content = "" replacing_content += "\n".join(formatted_names) replacing_content += "\n\n" @@ -398,6 +409,7 @@ def get_generic_doc() -> str: if __name__ == "__main__": file_path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_OUTPUT_PATH - generate_api(file_path) - generate_doc(PATH_TO_ECAD_DOC_FILE, get_ecad_doc()) - generate_doc(PATH_TO_GENERIC_DOC_FILE, get_generic_doc()) + file_path = Path(file_path) + _generate_api(file_path) + _generate_doc(PATH_TO_ECAD_DOC_FILE, _get_ecad_doc()) + _generate_doc(PATH_TO_GENERIC_DOC_FILE, _get_generic_doc()) diff --git a/tools/update_logo_version.py b/tools/update_logo_version.py index 4f9ba150..4d48b6ce 100644 --- a/tools/update_logo_version.py +++ b/tools/update_logo_version.py @@ -1,5 +1,6 @@ """ Icclim Logo updater. + This update the version number within icclim svg logo. It should only be used in our CI pipeline (github action). For testing purposes if you wish to run it locally you must first enable the git LFS @@ -9,25 +10,34 @@ from __future__ import annotations import sys +from pathlib import Path import icclim VERSION_PLACEHOLDER = "{{icclim.__version__}}" +MAX_ARGS_COUNT = 2 + -def run(inpath, outpath): - with open(inpath) as in_file, open(outpath, "w") as out_file: +def _run(inpath: Path, outpath: Path) -> None: + with Path.open(inpath) as in_file, Path.open(outpath, "w") as out_file: for line in in_file: if VERSION_PLACEHOLDER in line: - line = line.replace(VERSION_PLACEHOLDER, str(icclim.__version__)) - out_file.write(line) + updated_line = line.replace( + VERSION_PLACEHOLDER, + str(icclim.__version__), + ) + out_file.write(updated_line) if __name__ == "__main__": - if len(sys.argv) < 3: - raise NotImplementedError( + if len(sys.argv) <= MAX_ARGS_COUNT: + error_msg = ( "This script needs 2 arguments," " the input file path where a placeholder exists" " and the output file path" ) - run(sys.argv[1], sys.argv[2]) + raise NotImplementedError(error_msg) + in_path = Path(sys.argv[1]) + out_path = Path(sys.argv[1]) + _run(in_path, out_path)