From b197bec8fd572f2b06da55c74cbcb45dad5d640e Mon Sep 17 00:00:00 2001 From: Trevor James Smith <10819524+Zeitsperre@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:41:23 -0400 Subject: [PATCH 1/7] employ a src layout for code base --- .flake8 | 2 +- .github/labeler.yml | 10 +- .github/workflows/bump-version.yml | 6 +- .github/workflows/codeql.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/publish-mastodon.yml | 2 +- .github/workflows/upstream.yml | 2 +- .gitignore | 1 - .pre-commit-config.yaml | 4 +- .readthedocs.yml | 4 +- .yamllint.yaml | 31 ++ CHANGELOG.rst | 8 + CONTRIBUTING.rst | 297 +++++++++++------- Makefile | 26 +- docs/conf.py | 2 +- docs/installation.rst | 43 +-- docs/sdba.rst | 1 + pyproject.toml | 35 ++- {xclim => src/xclim}/__init__.py | 0 {xclim => src/xclim}/analog.py | 0 {xclim => src/xclim}/cli.py | 10 +- {xclim => src/xclim}/core/__init__.py | 0 {xclim => src/xclim}/core/_exceptions.py | 0 {xclim => src/xclim}/core/_types.py | 0 {xclim => src/xclim}/core/bootstrapping.py | 0 {xclim => src/xclim}/core/calendar.py | 0 {xclim => src/xclim}/core/cfchecks.py | 0 {xclim => src/xclim}/core/datachecks.py | 0 {xclim => src/xclim}/core/dataflags.py | 0 {xclim => src/xclim}/core/formatting.py | 0 {xclim => src/xclim}/core/indicator.py | 0 {xclim => src/xclim}/core/locales.py | 0 {xclim => src/xclim}/core/missing.py | 0 {xclim => src/xclim}/core/options.py | 0 {xclim => src/xclim}/core/units.py | 0 {xclim => src/xclim}/core/utils.py | 0 {xclim => src/xclim}/data/__init__.py | 0 {xclim => src/xclim}/data/anuclim.yml | 0 {xclim => src/xclim}/data/cf.yml | 0 {xclim => src/xclim}/data/fr.json | 0 {xclim => src/xclim}/data/icclim.yml | 0 {xclim => src/xclim}/data/schema.yml | 0 {xclim => src/xclim}/data/variables.yml | 0 {xclim => src/xclim}/ensembles/__init__.py | 0 {xclim => src/xclim}/ensembles/_base.py | 0 {xclim => src/xclim}/ensembles/_filters.py | 0 .../xclim}/ensembles/_partitioning.py | 0 {xclim => src/xclim}/ensembles/_reduce.py | 0 {xclim => src/xclim}/ensembles/_robustness.py | 0 {xclim => src/xclim}/indicators/__init__.py | 0 .../xclim}/indicators/atmos/__init__.py | 0 .../xclim}/indicators/atmos/_conversion.py | 0 .../xclim}/indicators/atmos/_precip.py | 0 .../xclim}/indicators/atmos/_synoptic.py | 0 .../xclim}/indicators/atmos/_temperature.py | 0 .../xclim}/indicators/atmos/_wind.py | 0 .../xclim}/indicators/generic/__init__.py | 0 .../xclim}/indicators/generic/_stats.py | 0 .../xclim}/indicators/land/__init__.py | 0 {xclim => src/xclim}/indicators/land/_snow.py | 0 .../xclim}/indicators/land/_streamflow.py | 0 .../xclim}/indicators/seaIce/__init__.py | 0 .../xclim}/indicators/seaIce/_seaice.py | 0 {xclim => src/xclim}/indices/__init__.py | 0 {xclim => src/xclim}/indices/_agro.py | 0 {xclim => src/xclim}/indices/_anuclim.py | 0 {xclim => src/xclim}/indices/_conversion.py | 0 {xclim => src/xclim}/indices/_hydrology.py | 0 {xclim => src/xclim}/indices/_multivariate.py | 0 {xclim => src/xclim}/indices/_simple.py | 0 {xclim => src/xclim}/indices/_synoptic.py | 0 {xclim => src/xclim}/indices/_threshold.py | 0 {xclim => src/xclim}/indices/fire/__init__.py | 0 {xclim => src/xclim}/indices/fire/_cffwis.py | 0 {xclim => src/xclim}/indices/fire/_ffdi.py | 0 {xclim => src/xclim}/indices/generic.py | 0 {xclim => src/xclim}/indices/helpers.py | 0 {xclim => src/xclim}/indices/run_length.py | 0 {xclim => src/xclim}/indices/stats.py | 0 {xclim => src/xclim}/sdba/__init__.py | 0 {xclim => src/xclim}/sdba/_adjustment.py | 0 {xclim => src/xclim}/sdba/_processing.py | 0 {xclim => src/xclim}/sdba/adjustment.py | 0 {xclim => src/xclim}/sdba/base.py | 0 {xclim => src/xclim}/sdba/detrending.py | 0 {xclim => src/xclim}/sdba/loess.py | 0 {xclim => src/xclim}/sdba/measures.py | 0 {xclim => src/xclim}/sdba/nbutils.py | 0 {xclim => src/xclim}/sdba/processing.py | 0 {xclim => src/xclim}/sdba/properties.py | 0 {xclim => src/xclim}/sdba/utils.py | 0 {xclim => src/xclim}/testing/__init__.py | 0 {xclim => src/xclim}/testing/conftest.py | 0 {xclim => src/xclim}/testing/diagnostics.py | 0 {xclim => src/xclim}/testing/helpers.py | 0 {xclim => src/xclim}/testing/registry.txt | 0 {xclim => src/xclim}/testing/sdba_utils.py | 0 {xclim => src/xclim}/testing/utils.py | 2 +- tests/test_cli.py | 9 +- tests/test_modules.py | 2 +- tests/test_testing_utils.py | 21 +- tox.ini | 17 +- 102 files changed, 334 insertions(+), 205 deletions(-) rename {xclim => src/xclim}/__init__.py (100%) rename {xclim => src/xclim}/analog.py (100%) rename {xclim => src/xclim}/cli.py (98%) rename {xclim => src/xclim}/core/__init__.py (100%) rename {xclim => src/xclim}/core/_exceptions.py (100%) rename {xclim => src/xclim}/core/_types.py (100%) rename {xclim => src/xclim}/core/bootstrapping.py (100%) rename {xclim => src/xclim}/core/calendar.py (100%) rename {xclim => src/xclim}/core/cfchecks.py (100%) rename {xclim => src/xclim}/core/datachecks.py (100%) rename {xclim => src/xclim}/core/dataflags.py (100%) rename {xclim => src/xclim}/core/formatting.py (100%) rename {xclim => src/xclim}/core/indicator.py (100%) rename {xclim => src/xclim}/core/locales.py (100%) rename {xclim => src/xclim}/core/missing.py (100%) rename {xclim => src/xclim}/core/options.py (100%) rename {xclim => src/xclim}/core/units.py (100%) rename {xclim => src/xclim}/core/utils.py (100%) rename {xclim => src/xclim}/data/__init__.py (100%) rename {xclim => src/xclim}/data/anuclim.yml (100%) rename {xclim => src/xclim}/data/cf.yml (100%) rename {xclim => src/xclim}/data/fr.json (100%) rename {xclim => src/xclim}/data/icclim.yml (100%) rename {xclim => src/xclim}/data/schema.yml (100%) rename {xclim => src/xclim}/data/variables.yml (100%) rename {xclim => src/xclim}/ensembles/__init__.py (100%) rename {xclim => src/xclim}/ensembles/_base.py (100%) rename {xclim => src/xclim}/ensembles/_filters.py (100%) rename {xclim => src/xclim}/ensembles/_partitioning.py (100%) rename {xclim => src/xclim}/ensembles/_reduce.py (100%) rename {xclim => src/xclim}/ensembles/_robustness.py (100%) rename {xclim => src/xclim}/indicators/__init__.py (100%) rename {xclim => src/xclim}/indicators/atmos/__init__.py (100%) rename {xclim => src/xclim}/indicators/atmos/_conversion.py (100%) rename {xclim => src/xclim}/indicators/atmos/_precip.py (100%) rename {xclim => src/xclim}/indicators/atmos/_synoptic.py (100%) rename {xclim => src/xclim}/indicators/atmos/_temperature.py (100%) rename {xclim => src/xclim}/indicators/atmos/_wind.py (100%) rename {xclim => src/xclim}/indicators/generic/__init__.py (100%) rename {xclim => src/xclim}/indicators/generic/_stats.py (100%) rename {xclim => src/xclim}/indicators/land/__init__.py (100%) rename {xclim => src/xclim}/indicators/land/_snow.py (100%) rename {xclim => src/xclim}/indicators/land/_streamflow.py (100%) rename {xclim => src/xclim}/indicators/seaIce/__init__.py (100%) rename {xclim => src/xclim}/indicators/seaIce/_seaice.py (100%) rename {xclim => src/xclim}/indices/__init__.py (100%) rename {xclim => src/xclim}/indices/_agro.py (100%) rename {xclim => src/xclim}/indices/_anuclim.py (100%) rename {xclim => src/xclim}/indices/_conversion.py (100%) rename {xclim => src/xclim}/indices/_hydrology.py (100%) rename {xclim => src/xclim}/indices/_multivariate.py (100%) rename {xclim => src/xclim}/indices/_simple.py (100%) rename {xclim => src/xclim}/indices/_synoptic.py (100%) rename {xclim => src/xclim}/indices/_threshold.py (100%) rename {xclim => src/xclim}/indices/fire/__init__.py (100%) rename {xclim => src/xclim}/indices/fire/_cffwis.py (100%) rename {xclim => src/xclim}/indices/fire/_ffdi.py (100%) rename {xclim => src/xclim}/indices/generic.py (100%) rename {xclim => src/xclim}/indices/helpers.py (100%) rename {xclim => src/xclim}/indices/run_length.py (100%) rename {xclim => src/xclim}/indices/stats.py (100%) rename {xclim => src/xclim}/sdba/__init__.py (100%) rename {xclim => src/xclim}/sdba/_adjustment.py (100%) rename {xclim => src/xclim}/sdba/_processing.py (100%) rename {xclim => src/xclim}/sdba/adjustment.py (100%) rename {xclim => src/xclim}/sdba/base.py (100%) rename {xclim => src/xclim}/sdba/detrending.py (100%) rename {xclim => src/xclim}/sdba/loess.py (100%) rename {xclim => src/xclim}/sdba/measures.py (100%) rename {xclim => src/xclim}/sdba/nbutils.py (100%) rename {xclim => src/xclim}/sdba/processing.py (100%) rename {xclim => src/xclim}/sdba/properties.py (100%) rename {xclim => src/xclim}/sdba/utils.py (100%) rename {xclim => src/xclim}/testing/__init__.py (100%) rename {xclim => src/xclim}/testing/conftest.py (100%) rename {xclim => src/xclim}/testing/diagnostics.py (100%) rename {xclim => src/xclim}/testing/helpers.py (100%) rename {xclim => src/xclim}/testing/registry.txt (100%) rename {xclim => src/xclim}/testing/sdba_utils.py (100%) rename {xclim => src/xclim}/testing/utils.py (99%) diff --git a/.flake8 b/.flake8 index 6aa94ce03..b19731960 100644 --- a/.flake8 +++ b/.flake8 @@ -12,7 +12,7 @@ ignore = F, W503 per-file-ignores = - xclim/core/locales.py:RST399 + src/xclim/core/locales.py:RST399 rst-directives = bibliography, autolink-skip diff --git a/.github/labeler.yml b/.github/labeler.yml index 0d632792c..ec08dfb76 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -40,10 +40,10 @@ 'indicators': - changed-files: - any-glob-to-any-file: - - 'xclim/indicators/**/*' - - 'xclim/indices/**/_*.py' - - 'xclim/data/**/*.json' - - 'xclim/data/**/*.yml' + - 'src/xclim/indicators/**/*' + - 'src/xclim/indices/**/_*.py' + - 'src/xclim/data/**/*.json' + - 'src/xclim/data/**/*.yml' 'information': - changed-files: @@ -53,5 +53,5 @@ 'sdba': - changed-files: - any-glob-to-any-file: - - 'xclim/sdba/**/*' + - 'src/xclim/sdba/**/*' - 'tests/test_sdba/*.py' diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index 6b7c21cc8..2713d3e1c 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -21,8 +21,8 @@ on: - environment.yml - pylintrc - pyproject.toml + - src/xclim/__init__.py - tox.ini - - xclim/__init__.py permissions: contents: read @@ -68,7 +68,7 @@ jobs: trust_level: 5 - name: Current Version run: | - CURRENT_VERSION="$(grep -E '__version__' xclim/__init__.py | cut -d ' ' -f3)" + CURRENT_VERSION="$(grep -E '__version__' src/xclim/__init__.py | cut -d ' ' -f3)" echo "current_version=${CURRENT_VERSION}" echo "CURRENT_VERSION=${CURRENT_VERSION}" >> $GITHUB_ENV - name: Install CI libraries @@ -83,7 +83,7 @@ jobs: echo "Version is stable, bumping 'patch' version" bump-my-version bump patch fi - NEW_VERSION="$(grep -E '__version__' xclim/__init__.py | cut -d ' ' -f3)" + NEW_VERSION="$(grep -E '__version__' src/xclim/__init__.py | cut -d ' ' -f3)" echo "new_version=${NEW_VERSION}" echo "NEW_VERSION=${NEW_VERSION}" >> $GITHUB_ENV - name: Push Changes diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1287bca58..780b2c964 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -10,7 +10,7 @@ on: - Makefile - pyproject.toml - tox.ini - - xclim/__init__.py + - src/xclim/__init__.py - docs/*/*.ipynb - docs/*/*.py - docs/*/*.rst diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 08673fd99..359755ae4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: - CHANGELOG.rst - README.rst - pyproject.toml - - xclim/__init__.py + - src/xclim/__init__.py pull_request: types: - opened diff --git a/.github/workflows/publish-mastodon.yml b/.github/workflows/publish-mastodon.yml index 002fe7a77..2a4cd0e69 100644 --- a/.github/workflows/publish-mastodon.yml +++ b/.github/workflows/publish-mastodon.yml @@ -79,7 +79,7 @@ jobs: echo "${{ steps.render_template.outputs.result }}${{ env.contributors }}" - name: Send toot to Mastodon - if: ${{ github.event.inputs.dry-run != 'true' }} || ${{ github.event_name == 'release' }} + if: ${{ github.event.inputs.dry-run != 'true' || github.event_name == 'release' }} uses: cbrgm/mastodon-github-action@b26d62619432b20c2129edd86f07f7ede9797fc9 # v2.1.9 with: url: ${{ secrets.MASTODON_URL }} diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml index 612f20c97..336d072c0 100644 --- a/.github/workflows/upstream.yml +++ b/.github/workflows/upstream.yml @@ -7,7 +7,7 @@ on: - CHANGELOG.rst - README.rst - pyproject.toml - - xclim/__init__.py + - src/xclim/__init__.py schedule: - cron: "0 0 * * *" # Daily “At 00:00” UTC workflow_dispatch: # allows you to trigger the workflow run manually diff --git a/.gitignore b/.gitignore index a9e772152..a92c3a916 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ parts/ sdist/ var/ wheels/ -src/ *.egg-info/ .installed.cfg *.egg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c10978044..5a370c6b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: hooks: - id: pyupgrade args: ['--py39-plus'] - exclude: 'xclim/core/indicator.py' + exclude: 'src/xclim/core/indicator.py' - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: @@ -91,7 +91,7 @@ repos: hooks: - id: blackdoc additional_dependencies: [ 'black==24.10.0' ] - exclude: '(xclim/indices/__init__.py|docs/installation.rst)' + exclude: '(src/xclim/indices/__init__.py|docs/installation.rst)' - id: blackdoc-autoupdate-black - repo: https://github.com/codespell-project/codespell rev: v2.3.0 diff --git a/.readthedocs.yml b/.readthedocs.yml index a7eb8df50..719c9d5b1 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,9 +13,9 @@ build: python: "mambaforge-22.9" jobs: pre_build: - - sphinx-apidoc -o docs/apidoc/ --private --module-first xclim xclim/testing/tests xclim/indicators xclim/indices + - sphinx-apidoc -o docs/apidoc/ --private --module-first xclim src/xclim/testing/tests src/xclim/indicators src/xclim/indices - rm docs/apidoc/xclim.rst - - env SPHINX_APIDOC_OPTIONS="members,undoc-members,show-inheritance,noindex" sphinx-apidoc -o docs/apidoc/ --private --module-first xclim xclim/testing/tests + - env SPHINX_APIDOC_OPTIONS="members,undoc-members,show-inheritance,noindex" sphinx-apidoc -o docs/apidoc/ --private --module-first xclim src/xclim/testing/tests - sphinx-build -b linkcheck docs/ _build/linkcheck || true conda: diff --git a/.yamllint.yaml b/.yamllint.yaml index 2f3b4a3d2..5f8a8693f 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -1,8 +1,39 @@ --- rules: + + brackets: + forbid: false + min-spaces-inside: 0 + max-spaces-inside: 1 + + commas: + min-spaces-after: 1 + document-start: disable + + float-values: + require-numeral-before-decimal: true + + hyphens: + max-spaces-after: 1 + + indentation: + indent-sequences: whatever + spaces: consistent + + key-duplicates: + forbid-duplicated-merge-keys: true + line-length: + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true max: 120 level: warning + + new-lines: + type: unix + + trailing-spaces: {} + truthy: disable diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 612b745b1..cd726602f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog ========= +v0.54.0 (unreleased) +-------------------- +Contributors to this version: Trevor James Smith (:user:`Zeitsperre`). + +Internal changes +^^^^^^^^^^^^^^^^ +* `xclim` now uses a `src` layout for the codebase. Structure-dependent functions, documentation, and build commands have been adapted to reflect these changes. + v0.53.1 (2024-10-21) -------------------- Contributors to this version: Trevor James Smith (:user:`Zeitsperre`). diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0e6a1e73c..78194f870 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,5 +1,3 @@ -.. highlight:: console - ============ Contributing ============ @@ -27,7 +25,7 @@ General to-do list for implementing a new Indicator: * Their input arguments should have type annotations, as documented in :py:class:`~xclim.core.utils.InputKind` * Their docstring should follow the scheme explained in :ref:`notebooks/extendxclim:Defining new indices`. * They should set the units on their outputs, but no other metadata fields. - * Their code should be found in the most relevant ``xclim/indices/_*.py`` file. Functions are explicitly added to the ``__all__`` at the top of the file. + * Their code should be found in the most relevant ``src/xclim/indices/_*.py`` file. Functions are explicitly added to the ``__all__`` at the top of the file. #. Add unit tests @@ -37,7 +35,7 @@ General to-do list for implementing a new Indicator: #. Add the indicator * See :ref:`notebooks/extendxclim:Defining new indicators` for more info and look at the other indicators for inspiration. - * They are added in the most relevant ``xclim/indicators/{realm}/_*.py`` file. + * They are added in the most relevant ``src/xclim/indicators/{realm}/_*.py`` file. * Indicator are instances of subclasses of :py:class:`xclim.core.indicator.Indicator`. They should use a class declared within the ``{realm}`` folder, creating a dummy one if needed. They are explicitly added to the file's ``__all__``. @@ -51,7 +49,7 @@ General to-do list for implementing a new Indicator: #. Add French translations xclim comes with an internationalization module and all "official" indicators - (those in ``xclim.atmos.indicators``) must have a french translation added to ``xclim/data/fr.json``. + (those in ``xclim.atmos.indicators``) must have a french translation added to ``src/xclim/data/fr.json``. This part can be done by the core team after you open a Pull Request. .. note:: @@ -59,8 +57,8 @@ General to-do list for implementing a new Indicator: General notes for implementing new bias-adjustment methods: -* Method are implemented as classes in ``xclim/sdba/adjustment.py``. -* If the algorithm gets complicated and would generate many dask tasks, it should be implemented as functions wrapped by :py:func:`~xclim.sdba.map_blocks` or :py:func:`~xclim.sdba.map_groups` in ``xclim/sdba/_adjustment.py``. +* Method are implemented as classes in ``src/xclim/sdba/adjustment.py``. +* If the algorithm gets complicated and would generate many dask tasks, it should be implemented as functions wrapped by :py:func:`~xclim.sdba.map_blocks` or :py:func:`~xclim.sdba.map_groups` in ``src/xclim/sdba/_adjustment.py``. * `xclim` doesn't implement monolithic multi-parameter methods, but rather smaller modular functions to construct post-processing workflows. * If you are working on numba-accelerated function that uses ``@guvectorize``, consider disabling caching during the development phase and reactivating it once all changes are ready for review. This is done by commenting ``cache=True`` in the decorator. @@ -114,65 +112,83 @@ Ready to contribute? Here's how to set up `xclim` for local development. #. Fork the `xclim` repo on GitHub. -#. Clone your fork locally:: +#. Clone your fork locally: - $ git clone git@github.com:{my_github_username}/xclim.git - $ cd xclim/ + .. code-block:: shell -#. Create a development environment. We recommend using ``conda``:: + git clone git@github.com:{my_github_username}/xclim.git + cd xclim/ - $ conda create -n xclim python=3.10 --file=environment.yml - $ python -m pip install -e --no-deps . +#. Create a development environment. We recommend using ``conda``: -#. Create a branch for local development:: + .. code-block:: shell - $ git checkout -b name-of-your-bugfix-or-feature + conda create -n xclim python=3.10 --file=environment.yml + python -m pip install -e --no-deps . - Now you can make your changes locally! +#. Create a branch for local development: -#. Before committing your changes, we ask that you install ``pre-commit`` in your development environment. Pre-commit runs git hooks that ensure that your code resembles that of the project and catches and corrects any small errors or inconsistencies when you ``git commit``:: + .. code-block:: shell - # To install the necessary pre-commit hooks: - $ pre-commit install - # To run pre-commit hooks manually: - $ pre-commit run --all-files + git checkout -b name-of-your-bugfix-or-feature - Instead of ``pre-commit``, you can also verify your changes using the `Make` recipe for code linting checks:: + Now you can make your changes locally! - $ make lint +#. Before committing your changes, we ask that you install ``pre-commit`` in your development environment. Pre-commit runs git hooks that ensure that your code resembles that of the project and catches and corrects any small errors or inconsistencies when you ``git commit``: - Or, alternatively, you can check individual hooks manually with `black`, `isort`, `ruff`, `flake8`, `flake8-rst-docstrings`, `nbqa`, `blackdoc`, and `yamllint`:: + .. code-block:: shell - $ black --check xclim tests - $ isort --check xclim tests - $ ruff xclim tests - $ flake8 --config=.flake8 xclim tests - $ nbqa black --check docs - $ nbqa isort --check docs - $ blackdoc --check --exclude=xclim/indices/__init__.py xclim - $ blackdoc --check docs - $ yamllint --config-file=.yamllint.yaml xclim + # To install the necessary pre-commit hooks: + pre-commit install + # To run pre-commit hooks manually: + pre-commit run --all-files -#. When features or bug fixes have been contributed, unit tests and doctests have been added, or notebooks have been updated, use ``$ pytest`` to test them:: + Instead of ``pre-commit``, you can also verify your changes using the `Make` recipe for code linting checks: - $ pytest --no-cov --nbval --dist=loadscope --rootdir=tests/ docs/notebooks --ignore=docs/notebooks/example.ipynb # for notebooks, exclusively. - $ pytest --no-cov --rootdir=tests/ --xdoctest xclim # for doctests, exclusively. - $ pytest # for all unit tests, excluding doctests and notebooks. - $ pytest -m "not slow" # for all unit tests, excluding doctests, notebooks, and "slow" marked tests. + .. code-block:: shell - Alternatively, one can use ``$ tox`` to run very specific testing configurations, as GitHub Workflows would do when a Pull Request is submitted and new commits are pushed:: + make lint - $ tox -e py310-coverage # run tests on Python 3.10, reporting code coverage - $ tox -e py313-upstream # run tests on Python 3.13, with upstream dependencies - $ tox -e py311-prefetch-offline -- -m "not slow" # run tests on Python 3.11, force download of testing, ensure tests are all offline, exclude "slow" marked tests - $ tox -e py312-lmoments -- -m "not slow" # run tests on Python 3.12, installing lmoments3, excluding "slow" marked tests - $ tox -e notebooks,doctests # run the notebook-based tests, then run the doctests + Or, alternatively, you can check individual hooks manually with `black`, `isort`, `ruff`, `flake8`, `flake8-rst-docstrings`, `nbqa`, `blackdoc`, and `yamllint`: - $ tox -m test # run the standard tests used in GitHub Workflows + .. code-block:: shell - .. warning:: + black --check xclim tests + isort --check xclim tests + ruff xclim tests + flake8 --config=.flake8 xclim tests + nbqa black --check docs + nbqa isort --check docs + blackdoc --check --exclude=src/xclim/indices/__init__.py xclim + blackdoc --check docs + yamllint --config-file=.yamllint.yaml xclim + +#. When features or bug fixes have been contributed, unit tests and doctests have been added, or notebooks have been updated, use ``$ pytest`` to test them: + + .. code-block:: shell + + pytest --no-cov --nbval --dist=loadscope --rootdir=tests/ docs/notebooks --ignore=docs/notebooks/example.ipynb # for notebooks, exclusively. + pytest --no-cov --rootdir=tests/ --xdoctest src/xclim # for doctests, exclusively. + pytest # for all unit tests, excluding doctests and notebooks. + pytest -m "not slow" # for all unit tests, excluding doctests, notebooks, and "slow" marked tests. + + Alternatively, one can use ``$ tox`` to run very specific testing configurations, as GitHub Workflows would do when a Pull Request is submitted and new commits are pushed: + + .. code-block:: shell + + tox -e py310-coverage # run tests on Python 3.10, reporting code coverage + tox -e py313-upstream # run tests on Python 3.13, with upstream dependencies + tox -e py311-prefetch-offline -- -m "not slow" # run tests on Python 3.11, force download of testing, ensure tests are all offline, exclude "slow" marked tests + tox -e py312-lmoments -- -m "not slow" # run tests on Python 3.12, installing lmoments3, excluding "slow" marked tests + tox -e notebooks,doctests # run the notebook-based tests, then run the doctests + + tox -m test # run the standard tests used in GitHub Workflows + + .. warning:: - Starting from `xclim` v0.46.0, when running tests with `tox`, any `pytest` markers passed to `pyXX` builds (e.g. `-m "not slow"`) must be passed to `tox` directly. This can be done as follows:: + Starting from `xclim` v0.46.0, when running tests with `tox`, any `pytest` markers passed to `pyXX` builds (e.g. `-m "not slow"`) must be passed to `tox` directly. This can be done as follows: + + .. code-block:: shell $ tox -e py310 -- -m "not slow" @@ -180,39 +196,45 @@ Ready to contribute? Here's how to set up `xclim` for local development. `notebooks` and `doctests`: these configurations do not pass test markers to its `pytest` call. `offline`: this configuration runs by default with the `-m "not requires_internet"` test marker. Be aware that running `tox` and manually setting a `pytest` marker will override this default. - .. note:: + .. note:: - `xclim` tests are organized to support the `pytest-xdist`_ plugin for distributed testing across workers or CPUs. - In order to benefit from multiple processes, add the flag `--numprocesses=auto` or `-n auto` to your `pytest` calls. + `xclim` tests are organized to support the `pytest-xdist`_ plugin for distributed testing across workers or CPUs. + In order to benefit from multiple processes, add the flag `--numprocesses=auto` or `-n auto` to your `pytest` calls. - When running tests via `tox`, `numprocesses` is set to the number of logical cores available (`numprocesses=logical`), with a maximum amount of `8`. + When running tests via `tox`, `numprocesses` is set to the number of logical cores available (`numprocesses=logical`), with a maximum amount of `8`. -#. Docs should also be tested to ensure that the documentation will build correctly on ReadTheDocs. This can be performed in a number of ways:: +#. Docs should also be tested to ensure that the documentation will build correctly on ReadTheDocs. This can be performed in a number of ways: - # To run in a contained virtualenv environment - $ tox -e docs - # or, alternatively, to build the docs directly - $ make docs + .. code-block:: shell - .. note:: + # To run in a contained virtualenv environment + $ tox -e docs + # or, alternatively, to build the docs directly + $ make docs + + .. note:: + + When building the documentation, the default behaviour is to evaluate notebooks ('`nbsphinx_execute = "auto"`'), rather than simply parse the content ('`nbsphinx_execute = "never"`'). + Due to their complexity, this is a very computationally demanding task and should only be performed when necessary (i.e.: when the notebooks have been modified). - When building the documentation, the default behaviour is to evaluate notebooks ('`nbsphinx_execute = "auto"`'), rather than simply parse the content ('`nbsphinx_execute = "never"`'). - Due to their complexity, this is a very computationally demanding task and should only be performed when necessary (i.e.: when the notebooks have been modified). + In order to speed up documentation builds, setting a value for the environment variable "`SKIP_NOTEBOOKS`" (e.g. "`$ export SKIP_NOTEBOOKS=1`") will prevent the notebooks from being evaluated on all subsequent "`$ tox -e docs`" or "`$ make docs`" invocations. - In order to speed up documentation builds, setting a value for the environment variable "`SKIP_NOTEBOOKS`" (e.g. "`$ export SKIP_NOTEBOOKS=1`") will prevent the notebooks from being evaluated on all subsequent "`$ tox -e docs`" or "`$ make docs`" invocations. +#. After clearing the previous checks, commit your changes and push your branch to GitHub: -#. After clearing the previous checks, commit your changes and push your branch to GitHub:: + .. code-block:: shell - $ git add * - $ git commit -m "Your detailed description of your changes." + git add * + git commit -m "Your detailed description of your changes." - If installed, `pre-commit` will run checks at this point: + If installed, `pre-commit` will run checks at this point: - * If no errors are found, changes will be committed. - * If errors are found, modifications will be made and warnings will be raised if intervention is needed. - * After addressing errors and effecting changes, simply `git commit` again:: + * If no errors are found, changes will be committed. + * If errors are found, modifications will be made and warnings will be raised if intervention is needed. + * After addressing errors and effecting changes, simply `git commit` again: - $ git push origin name-of-your-bugfix-or-feature + .. code-block:: shell + + git push origin name-of-your-bugfix-or-feature #. Submit a pull request through the GitHub website. @@ -225,16 +247,19 @@ Before you submit a pull request, please follow these guidelines: #. Perform the changes, commit and push them either to new a branch within `Ouranosinc/xclim` or to your personal fork of xclim. - .. warning:: - Try to keep your contributions within the scope of the issue that you are addressing. - While it might be tempting to fix other aspects of the library as it comes up, - it's better to simply to flag the problems in case others are already working on it. + .. warning:: - Consider adding a "**# TODO:**" or "**# FIXME:**" comment if the need arises. + Try to keep your contributions within the scope of the issue that you are addressing. + While it might be tempting to fix other aspects of the library as it comes up, + it's better to simply to flag the problems in case others are already working on it. + + Consider adding a "**# TODO:**" or "**# FIXME:**" comment if the need arises. #. Pull requests should raise test coverage for the xclim library. Code coverage is an indicator of how extensively tested the library is. - If you are adding a new set of functions, they **must be tested** and **coverage percentage should not significantly decrease.** + .. note:: + + If you are adding a new set of functions, they **must be tested** and **coverage percentage should not significantly decrease.** #. If the pull request adds functionality, your functions should include docstring explanations. So long as the docstrings are syntactically correct, sphinx-autodoc will be able to automatically parse the information. @@ -244,8 +269,9 @@ Before you submit a pull request, please follow these guidelines: * `reStructuredText (ReST)`_ .. note:: - If you aren't accustomed to writing documentation in reStructuredText (`.rst`), we encourage you to spend a few minutes going over the - incredibly well-summarized `reStructuredText Primer`_ from the sphinx-doc maintainer community. + + If you aren't accustomed to writing documentation in reStructuredText (`.rst`), we encourage you to spend a few minutes going over the + incredibly well-summarized `reStructuredText Primer`_ from the sphinx-doc maintainer community. #. The pull request should work for all currently-supported Python versions as well as raise test coverage. Pull requests are also checked for documentation build status and for `PEP8`_ compliance. @@ -264,7 +290,9 @@ Before you submit a pull request, please follow these guidelines: #. The version changes (CHANGELOG.rst) should briefly describe changes introduced in the Pull request. Changes should be organized by type (ie: `New indicators`, `New features and enhancements`, `Breaking changes`, `Bug fixes`, `Internal changes`) and the GitHub Pull Request, GitHub Issue. - Your name and/or GitHub handle should also be listed among the contributors to this version. This can be done as follows:: + Your name and/or GitHub handle should also be listed among the contributors to this version. This can be done as follows: + +.. code-block:: restructuredtext Contributors to this version: John Jacob Jingleheimer Schmidt (:user:`username`). @@ -272,42 +300,52 @@ Before you submit a pull request, please follow these guidelines: ^^^^^^^^^^^^^^^^ * Updated the contribution guidelines. (:issue:`868`, :pull:`869`). - If this is your first contribution to `Ouranosinc/xclim`, we ask that you also add your name to the `AUTHORS.rst `_, under *Contributors* as well as to the `.zenodo.json `_, at the end of the *creators* block. +If this is your first contribution to `Ouranosinc/xclim`, we ask that you also add your name to the `AUTHORS.rst `_, under *Contributors* as well as to the `.zenodo.json `_, at the end of the *creators* block. Updating Testing Data ~~~~~~~~~~~~~~~~~~~~~ If your code changes require changes to the testing data of `xclim` (i.e.: modifications to existing datasets or new datasets), these changes must be made via a Pull Request at the `xclim-testdata repository`_. -`xclim` allows for developers to test specific branches/versions or forks of the `xclim-testdata` repository via the `XCLIM_TESTDATA_BRANCH` and `XCLIM_TESTDATA_REPO` environment variables, respectively, either through export, e.g.:: +`xclim` allows for developers to test specific branches/versions or forks of the `xclim-testdata` repository via the `XCLIM_TESTDATA_BRANCH` and `XCLIM_TESTDATA_REPO` environment variables, respectively, either through export, e.g.: + +.. code-block:: shell - $ export XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" - $ export XCLIM_TESTDATA_REPO="https://github.com/my_username/xclim-testdata" + export XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" + export XCLIM_TESTDATA_REPO="https://github.com/my_username/xclim-testdata" - $ pytest + pytest # or, alternatively: - $ tox + tox -or by setting the variable at runtime:: +or by setting the variable at runtime: - $ env XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" XCLIM_TESTDATA_REPO="https://github.com/my_username/xclim-testdata" pytest +.. code-block:: shell + + env XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" XCLIM_TESTDATA_REPO="https://github.com/my_username/xclim-testdata" pytest # or, alternatively: - $ env XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" XCLIM_TESTDATA_REPO="https://github.com/my_username/xclim-testdata" tox + env XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" XCLIM_TESTDATA_REPO="https://github.com/my_username/xclim-testdata" tox This will ensure that tests load the appropriate testing data from this branch or repository before running. -If you anticipate not having internet access, we suggest prefetching the testing data from `xclim-testdata repository`_ and storing it in your local cache. This can be done by running the following console command:: +If you anticipate not having internet access, we suggest prefetching the testing data from `xclim-testdata repository`_ and storing it in your local cache. This can be done by running the following console command: + +.. code-block:: shell - $ xclim prefetch_testing_data + xclim prefetch_testing_data -If your development branch relies on a specific branch of `Ouranosinc/xclim-testdata`, you can specify this using environment variables:: +If your development branch relies on a specific branch of `Ouranosinc/xclim-testdata`, you can specify this using environment variables: - $ export XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" - $ xclim prefetch_testing_data +.. code-block:: shell -or, alternatively, with the `--branch` option:: + export XCLIM_TESTDATA_BRANCH="my_new_branch_of_testing_data" + xclim prefetch_testing_data - $ xclim prefetch_testing_data --branch my_new_branch_of_testing_data --repo "https://github.com/my_username/xclim-testdata" +or, alternatively, with the `--branch` option: + +.. code-block:: shell + + xclim prefetch_testing_data --branch my_new_branch_of_testing_data --repo "https://github.com/my_username/xclim-testdata" If you wish to test a specific branch using GitHub CI, this can be set in `.github/workflows/main.yml`: @@ -326,13 +364,17 @@ Running Tests in Offline Mode `xclim` testing is designed with the assumption that the machine running the tests has internet access. Many calls to `xclim` functions will attempt to download data or verify checksums from the `Ouranosinc/xclim-testdata` repository. This can be problematic for developers working on features where internet access is not reliably available. -If you wish to ensure that your feature or bugfix can be developed without internet access, `xclim` leverages the `pytest-socket`_ plugin so that testing can be run in "offline" mode by invoking pytest with the following options:: +If you wish to ensure that your feature or bugfix can be developed without internet access, `xclim` leverages the `pytest-socket`_ plugin so that testing can be run in "offline" mode by invoking pytest with the following options: + +.. code-block:: shell - $ pytest --disable-socket --allow-unix-socket -m "not requires_internet" + pytest --disable-socket --allow-unix-socket -m "not requires_internet" -or, alternatively, using `tox` :: +or, alternatively, using `tox` : - $ tox -e offline +.. code-block:: shell + + tox -e offline These options will disable all network calls and skip tests marked with the ``requires_internet`` marker. The ``--allow-unix-socket`` option is required to allow the `pytest-xdist`_ plugin to function properly. @@ -340,25 +382,33 @@ The ``--allow-unix-socket`` option is required to allow the `pytest-xdist`_ plug Tips ---- -To run a subset of tests, we suggest a few approaches. For running only a test file:: +To run a subset of tests, we suggest a few approaches. For running only a test file: + +.. code-block:: shell - $ pytest tests/test_xclim.py + pytest tests/test_xclim.py -To skip all slow tests:: +To skip all slow tests: - $ pytest -m "not slow" +.. code-block:: shell -To run all conventions tests at once:: + pytest -m "not slow" - $ pre-commit run --all-files +To run all conventions tests at once: + +.. code-block:: shell + + pre-commit run --all-files Versioning ---------- In order to update and release the library to PyPI, it's good to use a semantic versioning scheme. -The method we use is as follows:: +The method we use is as follows: + +.. code-block:: shell - major.minor.patch-release + major.minor.patch-release **Major** releases denote major changes resulting in a stable API; @@ -379,20 +429,26 @@ This section serves as a reminder for the maintainers on how to prepare the libr When a new version has been minted (features have been successfully integrated test coverage and stability is adequate), maintainers should update the pip-installable package (wheel and source release) on PyPI as well as the binary on conda-forge. -From a new branch (e.g. `prepare-v123`), open a Pull Request and make sure all your changes to support a new version are committed (**update the entry for newest version in CHANGELOG.rst**), Then run:: +From a new branch (e.g. `prepare-v123`), open a Pull Request and make sure all your changes to support a new version are committed (**update the entry for newest version in CHANGELOG.rst**), then run: - $ bump-my-version bump