diff --git a/.gitignore b/.gitignore index 7b0401607..cef52ef05 100644 --- a/.gitignore +++ b/.gitignore @@ -109,7 +109,7 @@ ENV/ # autogenerated RestructuredText docs/apidoc/modules.rst docs/apidoc/xclim*.rst -docs/indicators.json +docs/_dynamic/indicators.json docs/variables.json .vscode diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1cd37f8a9..6d9f2ce7d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: - id: yamllint args: [ '--config-file=.yamllint.yaml' ] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.4.0 + rev: 24.4.1 hooks: - id: black - repo: https://github.com/PyCQA/isort @@ -66,7 +66,7 @@ repos: args: [ '--py39-plus' ] additional_dependencies: [ 'pyupgrade==3.15.2' ] - id: nbqa-black - additional_dependencies: [ 'black==24.4.0' ] + additional_dependencies: [ 'black==24.4.1' ] - id: nbqa-isort additional_dependencies: [ 'isort==5.13.2' ] - repo: https://github.com/kynan/nbstripout @@ -79,7 +79,7 @@ repos: rev: v0.3.9 hooks: - id: blackdoc - additional_dependencies: [ 'black==24.4.0' ] + additional_dependencies: [ 'black==24.4.1' ] exclude: '(xclim/indices/__init__.py|docs/installation.rst)' - repo: https://github.com/codespell-project/codespell rev: v2.2.6 diff --git a/.zenodo.json b/.zenodo.json index bc29d900e..585d12ba7 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -41,6 +41,11 @@ "affiliation": "Ouranos, Montréal, Québec, Canada", "orcid": "0000-0003-3389-9406" }, + { + "name": "Gammon, Sarah", + "affiliation": "Ouranos, Montréal, Québec, Canada", + "orcid": "0009-0007-6082-9063" + }, { "name": "Alegre, Raquel", "affiliation": "University College London, London, United Kingdom", diff --git a/AUTHORS.rst b/AUTHORS.rst index 17582a6b6..0504718e7 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -29,6 +29,7 @@ Contributors * David Caron `@davidcaron `_ * Carsten Ehbrecht `@cehbrecht `_ * Jeremy Fyke `@jeremyfyke `_ +* Sarah Gammon `@SarahG-579462 `_ * Tom Keel `@Thomasjkeel `_ * Marie-Pier Labonté `@marielabonte `_ * Ludwig Lierhammer `@ludwiglierhammer `_ diff --git a/CHANGES.rst b/CHANGES.rst index f550884d1..8f90ee6b9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,9 +13,8 @@ Announcements New features and enhancements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Indicator ``xclim.atmos.potential_evapotranspiration`` and indice ``xclim.indices.potential_evapotranspiration`` now accept a new value (`DA02`) for argument `method` implementing potential evapotranspiration based on Droogers and Allen (2002). (:issue:`1710`, :pull:`1723`). -* `ensemble_percentiles` now takes a `method` argument, accepting - 'interpolated_inverted_cdf', 'hazen', 'weibull', 'linear' (default), - 'median_unbiased' and 'normal_unbiased'. (:issue:`1694`, :pull:`1732`). +* ``xclim.ensembles.ensemble_percentiles`` now takes a `method` argument, accepting one of: `'interpolated_inverted_cdf'`, `'hazen'`, `'weibull'`, `'linear'` (default), `'median_unbiased'`, or `'normal_unbiased'`. (:issue:`1694`, :pull:`1732`). +* The documentation now uses the `furo `_ theme for Sphinx. This theme supports native "light" and "dark" modes, adaptive screen resolution, as well as provides a better navigation layout for pages housing long lists of entries (e.g. `indices`). (:issue:`1693`, :pull:`1731`). New indicators ^^^^^^^^^^^^^^ @@ -27,9 +26,10 @@ Breaking changes * The previously deprecated functions ``xclim.sdba.processing.construct_moving_yearly_window`` and ``xclim.sdba.processing.unpack_moving_yearly_window`` have been removed. These functions have been replaced by ``xclim.core.calendar.stack_periods`` and ``xclim.core.calendar.unstack_periods``. (:pull:`1717`). * The previously deprecated function ``xclim.ensembles.change_significance`` has been removed. (:pull:`1737`). * Indicators ``snw_season_length`` and ``snd_season_length`` have been modified, see above. -* The `hargeaves85`/`hg85` method for the ``potential_evapotranspiration`` indicator and indice has been modified for precision and consistency with recent academic literature. (:issue:`1710`, :pull:`1723`). +* The `'hargeaves85'`/`'hg85'` method for the ``potential_evapotranspiration`` indicator and indice has been modified for precision and consistency with recent academic literature. (:issue:`1710`, :pull:`1723`). * The `__getitem__` method of ``xclim.core.indicator.Parameter`` instances has been removed. Accessing members of ``Parameters`` now uniquely uses dot notation. (:pull:`1721`). * The obsolete function wrapper for generating Indicators ``xclim.core.utils.wrapped_partial`` has been removed. (:pull:`1721`). +* The default documentation theme has changed from `sphinx-rtd-theme` to `furo`; Several modifications to the documentation configuration and CSS overrides have been made to accommodate the changes. `furo` is now a `docs` dependency. (:issue:`1693`, :pull:`1731`). Bug fixes ^^^^^^^^^ @@ -41,6 +41,7 @@ Bug fixes * ``make_criteria`` now skips columns with NaNs across all realizations. (:pull:`1713`). * Fixed bug QuantileDeltaMapping adjustment not working for seasonal grouping (:issue:`1704`, :pull:`1716`). * The codebase has been adjusted to address several (~400) `mypy`-related errors attributable to inaccurate function call signatures and variable name shadowing. (:issue:`1719`, :pull:`1721`). +* ``xclim.core.formatting.generate_indicator_docstring`` has been modified to ensure that the `numpy`-docstrings of all Indicators are consistent in their formatting. (:pull:`1731`). Internal changes ^^^^^^^^^^^^^^^^ @@ -51,6 +52,9 @@ Internal changes * Added the `vulture` static code analysis tool for finding dead code to the development dependency list and linters (makefile, tox and pre-commit hooks). (:pull:`1717`). * Added error message when using `xclim.indices.stats.dist_method` with `nnlf` and included note in docstring. (:issue:`1683`, :pull:`1714`). * PEP8 rule `N802` is now enabled in the `ruff` formatter. Function names should follow `Snake case `_, with rare exceptions. (:pull:`1721`). +* Linting dependencies have been updated to the latest versions and made consistent across `environment.yml`, `pyproject.toml` and `tox.ini` files. (:pull:`1717`). +* Code styling for the documentation now uses `sas` ("light" theme) and `lightbulb` ("dark" theme) in order to ensure adequate contrast for code blocks. (:pull:`1731`). +* Added several CSS overrides related to the HTML elements generated by `xarray` in the notebook-sourced documentation. (:pull:`1731`). v0.48.2 (2024-02-26) -------------------- diff --git a/Makefile b/Makefile index d7f585984..f01f59948 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ clean-test: ## remove test and coverage artifacts lint: ## check style with flake8 and black black --check xclim tests isort --check xclim tests - ruff xclim tests + ruff check xclim tests flake8 --config=.flake8 xclim tests vulture xclim tests nbqa black --check docs @@ -101,8 +101,8 @@ ifndef READTHEDOCS python -m http.server 54345 --directory docs/_build/html/ endif -servedocs: docs ## compile the docs watching for changes - watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . +servedocs: autodoc-custom-index ## generate Sphinx HTML documentation, including API docs, but without indexes for for indices and indicators, and watch for changes + $(MAKE) -C docs livehtml release: dist ## package and upload a release flit publish dist/* diff --git a/README.rst b/README.rst index 03e6045a3..05d940f5a 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -====================================== -xclim: Climate services library |logo| -====================================== +=============================================================== +xclim: Climate services library |logo| |logo-dark| |logo-light| +=============================================================== +----------------------------+-----------------------------------------------------+ | Versions | |pypi| |conda| |versions| | @@ -184,9 +184,20 @@ This package was created with Cookiecutter_ and the `audreyfeldroy/cookiecutter- :target: https://github.com/psf/black :alt: Python Black -.. |logo| image:: https://raw.githubusercontent.com/Ouranosinc/xclim/main/docs/logos/xclim-logo-small.png +.. |logo| image:: https://raw.githubusercontent.com/Ouranosinc/xclim/main/docs/logos/xclim-logo-small-light.png :target: https://github.com/Ouranosinc/xclim :alt: Xclim + :class: xclim-logo-small no-theme + +.. |logo-light| image:: https://raw.githubusercontent.com/Ouranosinc/xclim/main/docs/logos/empty.png + :target: https://github.com/Ouranosinc/xclim + :alt: + :class: xclim-logo-small only-light-inline + +.. |logo-dark| image:: https://raw.githubusercontent.com/Ouranosinc/xclim/main/docs/logos/empty.png + :target: https://github.com/Ouranosinc/xclim + :alt: + :class: xclim-logo-small only-dark-inline .. |pre-commit| image:: https://results.pre-commit.ci/badge/github/Ouranosinc/xclim/main.svg :target: https://results.pre-commit.ci/latest/github/Ouranosinc/xclim/main diff --git a/docs/Makefile b/docs/Makefile index 44a51e3b1..687a5c241 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,8 +2,8 @@ # # You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build SPHINXPROJ = xclim SOURCEDIR = . BUILDDIR = _build @@ -18,3 +18,6 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +livehtml: + sphinx-autobuild --port 54345 --open-browser --delay 3 --re-ignore "$(BUILDDIR)|apidoc|Makefile|_dynamic/indicators.json|variables.json|__pycache__" "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/indsearch.js b/docs/_static/indsearch.js index 6ec3dcdd9..5efdfe267 100644 --- a/docs/_static/indsearch.js +++ b/docs/_static/indsearch.js @@ -26,7 +26,7 @@ let miniSearch = new MiniSearch({ }); // Populate search object with complete list of indicators -fetch('indicators.json') +fetch('_static/indicators.json') .then(data => data.json()) .then(data => { indicators = Object.entries(data).map(([k, v]) => { @@ -68,7 +68,7 @@ function makeVariableList(ind) { /* kv[0] is the variable name, kv[1] is the variable description. */ /* Convert kv[1] to a string literal */ const text = escapeHTML(kv[1]); - const tooltip = ``; + const tooltip = ``; return tooltip }).join(''); } diff --git a/docs/_static/style.css b/docs/_static/style.css index 5d18dca7c..88122a6e4 100644 --- a/docs/_static/style.css +++ b/docs/_static/style.css @@ -1,4 +1,4 @@ -@import url("theme.css"); +@import url("styles/furo.css"); .wy-side-nav-search>a img.logo, .wy-side-nav-search .wy-dropdown>a img.logo { @@ -37,8 +37,16 @@ td.name { font-weight: 500; } -tr:nth-child(even) { - background-color: #dddddd; +body div tr:nth-child(even), +body div.rendered_html tbody tr:nth-child(even) { + background-color: var(--color-background-table-rows-even); + color: var(--color-text-table-rows-even); +} + +body div tr:nth-child(odd), +body div.rendered_html tbody tr:nth-child(odd) { + background-color: var(--color-background-table-rows-odd); + color: var(--color-text-table-rows-odd); } dd { @@ -75,7 +83,8 @@ ul.simple li { .indElem { margin: 10px; padding: 10px; - background-color: #ddd; + background-color: var(--color-indicator-background); + color: var(--color-indicator-text); border-radius: 10px; } @@ -84,11 +93,19 @@ ul.simple li { } code > .indName { - background-color: #ccc; + background-color: var(--color-indicator-background); + color: var(--color-indicator-text); } .indVarname { border-radius: 10px; + background-color: var(--color-indicator-widget-background); + color: var(--color-indicator-widget-text); + border: solid 1px var(--color-indicator-widget-text); + margin-right: 3px; + margin-bottom: 3px; + line-height: 24px; + cursor: help; } /* Rounded corners for keyword labels: */ @@ -96,9 +113,79 @@ code > .indName { border-radius: 10px; padding: 5px; margin: 5px; - background-color: #ddd; + background-color: var(--color-indicator-widget-background); + color: var(--color-indicator-widget-text); + border: solid 1px var(--color-indicator-widget-text); + line-height: 24px; + } #incVirtModLbl { display: inline; } + +/* extend furo for inline ".only-dark" elements */ +body .only-dark-inline, +body .only-light-inline { + display: none !important; +} + +@media not print { + body[data-theme="dark"] .only-dark-inline, + body[data-theme="light"] .only-light-inline { + display: inline !important; + } + @media (prefers-color-scheme: dark) { + body:not([data-theme="light"]) .only-dark-inline{ + display: inline !important; + } + } + @media (prefers-color-scheme: light) { + body:not([data-theme="dark"]) .only-light-inline{ + display: inline !important; + } + } +} + +@media print { + .only-light-inline{ + display: inline !important; + } + .only-dark-inline{ + display: none !important; + } +} + +img.xclim-logo-small.only-dark-inline { + width: 91px; + height: 103px; + margin: 0; + padding: 0; + background-color: transparent; + background-repeat: no-repeat; + border-image-width: 0px; + border: none; + border-image-width: 0px; + background-image: url("xclim-logo-small-dark.png"); +} + +img.xclim-logo-small.only-light-inline { + width: 91px; + height: 103px; + margin: 0; + padding: 0; + background-color: transparent; + background-repeat: no-repeat; + border: none; + border-image-width: 0px; + background-image: url("xclim-logo-small-light.png"); +} + +img.xclim-logo-small.no-theme { + display: none; + width: 0px; +} + +button.copybtn.copybtn svg { + stroke: var(--color-copybutton); +} diff --git a/docs/_static/xarray.css b/docs/_static/xarray.css new file mode 100644 index 000000000..5ea01f05a --- /dev/null +++ b/docs/_static/xarray.css @@ -0,0 +1,13 @@ + +/* default xarray theme, this is taken from the injected css that xarray uses. +However, we change it so that it updates in the body, instead of :root, so that it updates with the theme.*/ +html, body { + --xr-font-color0: var(--jp-content-font-color0, rgba(0, 0, 0, 1)); + --xr-font-color2: var(--jp-content-font-color2, rgba(0, 0, 0, 0.54)); + --xr-font-color3: var(--jp-content-font-color3, rgba(0, 0, 0, 0.38)); + --xr-border-color: var(--jp-border-color2, #e0e0e0); + --xr-disabled-color: var(--jp-layout-color3, #bdbdbd); + --xr-background-color: var(--jp-layout-color0, white); + --xr-background-color-row-even: var(--jp-layout-color1, white); + --xr-background-color-row-odd: var(--jp-layout-color2, #eeeeee); +} diff --git a/docs/_templates/layout.html b/docs/_templates/base.html similarity index 92% rename from docs/_templates/layout.html rename to docs/_templates/base.html index 00954b22d..498bbd45b 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/base.html @@ -1,4 +1,4 @@ -{% extends "!layout.html" %} +{% extends "!base.html" %} {% set css_files = css_files + ["_static/style.css"] %} -{% block scripts %} +{% block site_meta %} {% if "indicators" in sourcename %} diff --git a/docs/api.rst b/docs/api.rst index 04e07fb96..8064ff8dc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,11 +2,6 @@ API === -.. contents:: Table of Contents - :depth: 1 - :local: - :backlinks: none - Indicators ========== diff --git a/docs/conf.py b/docs/conf.py index 80b7e05b4..b4e71d3a6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -65,7 +65,8 @@ # Dump indicators to json. The json is added to the html output (html_extra_path) # It is read by _static/indsearch.js to populate the table in indicators.rst -with open("indicators.json", "w") as f: +os.makedirs("_dynamic", exist_ok=True) +with open("_dynamic/indicators.json", "w") as f: json.dump(indicators, f) @@ -104,7 +105,6 @@ "sphinx_codeautolink", "sphinx_copybutton", "sphinx_mdinclude", - "sphinx_rtd_theme", ] autodoc_typehints = "description" @@ -272,8 +272,9 @@ class XCStyle(AlphaStyle): "**.ipynb_checkpoints", ] -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" +# The name of the Pygments (syntax highlighting) style to use for light and dark themes. +pygments_style = "sas" +pygments_dark_style = "lightbulb" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True @@ -283,31 +284,102 @@ class XCStyle(AlphaStyle): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_title = "XClim Official Documentation" -html_short_title = "XClim" - -html_theme = "sphinx_rtd_theme" - -html_extra_path = ["indicators.json", "variables.json"] - -# Theme options are theme-specific and customize the look and feel of a -# theme further. For a list of options available for each theme, see the -# documentation. -# -html_theme_options = {"logo_only": True, "style_external_links": True} +html_title = "xclim Official Documentation" +html_short_title = "xclim" + +html_theme = "furo" +html_extra_path = ["variables.json"] + +# Theme options are theme-specific and customize the look and feel of a theme further. +# For a list of options available for each theme, see the documentation. +html_theme_options = { + "light_logo": "xclim-logo-light.png", + "dark_logo": "xclim-logo-dark.png", + "footer_icons": [ + { + "name": "GitHub", + "url": "https://github.com/Ouranosinc/xclim", + "html": """ + + + + """, # noqa: E501 + "class": "", + }, + ], + "dark_css_variables": { + "color-background-table-rows-even": "#303335", + "color-background-table-rows-odd": "#3e3e3e", + "color-text-table-rows-even": "#fff", + "color-text-table-rows-odd": "#fff", + "color-copybutton": "#fff", + "color-indicator-text": "#cfd0d0", + "color-indicator-background": "#3e3e3e", + "color-indicator-widget-text": "#a8a8a8", + "color-indicator-widget-background": "#303335", + # Fix for xarray injected theme error in auto*dark mode + # Note: because these are set with the selector + # body:not([data-theme="light"]), any variable that uses them needs to + # have a scope smaller than body. + # However, the xarray variables that use these are defined in the :root selector, + # which is higher than body. We therefore need to redefine them in body. + # This is done in xarray.css, included at the bottom of this file. + # furo issue to track when this is no longer needed: + # https://github.com/pradyunsg/furo/discussions/790 + "jp-content-font-color0": "rgba(255, 255, 255, 1)", + "jp-content-font-color2": "rgba(255, 255, 255, 0.54)", + "jp-content-font-color3": "rgba(255, 255, 255, 0.38)", + "jp-border-color0": "#1F1F1F", + "jp-border-color1": "#1F1F1F", + "jp-border-color2": "#1F1F1F", + "jp-layout-color0": "#111111", + "jp-layout-color1": "#111111", + "jp-layout-color2": "#313131", + "jp-layout-color3": "#515151", + }, + "light_css_variables": { + "color-background-table-rows-even": "#eeebee", + "color-background-table-rows-odd": "#f5f5f5", + "color-text-table-rows-even": "#000", + "color-text-table-rows-odd": "#000", + "color-copybutton": "#000", + "color-indicator-text": "#5a5c63", + "color-indicator-background": "#eeebee", + "color-indicator-widget-text": "#2f2f2f", + "color-indicator-widget-background": "#bdbdbd", + # (consistency for light and dark themes, so variables are unset when switching to light) + "jp-content-font-color0": "rgba(0, 0, 0, 1)", + "jp-content-font-color2": "rgba(0, 0, 0, 0.54)", + "jp-content-font-color3": "rgba(0, 0, 0, 0.38)", + "jp-border-color0": "#e0e0e0", + "jp-border-color1": "#e0e0e0", + "jp-border-color2": "#e0e0e0", + "jp-layout-color0": "#ffffff", + "jp-layout-color1": "#ffffff", + "jp-layout-color2": "#eeeeee", + "jp-layout-color3": "#bdbdbd", + }, +} html_sidebars = { - "**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"] + "**": [ + "sidebar/scroll-start.html", + "sidebar/brand.html", + "sidebar/search.html", + "sidebar/navigation.html", + "sidebar/ethical-ads.html", + "sidebar/scroll-end.html", + ] } # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = "logos/xclim-logo.png" +# html_logo = "logos/xclim-logo.png" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +html_static_path = ["_dynamic", "logos", "_static"] # -- Options for HTMLHelp output --------------------------------------- @@ -317,7 +389,7 @@ class XCStyle(AlphaStyle): # -- Options for LaTeX output ------------------------------------------ latex_engine = "pdflatex" -latex_logo = "logos/xclim-logo.png" +latex_logo = "logos/xclim-logo-light.png" latex_elements = { # The paper size ('letterpaper' or 'a4paper'). @@ -379,4 +451,5 @@ class XCStyle(AlphaStyle): def setup(app): - app.add_css_file("_static/style.css") + app.add_css_file("style.css") + app.add_css_file("xarray.css") diff --git a/docs/installation.rst b/docs/installation.rst index 5141427b9..80ec03412 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -114,9 +114,9 @@ From sources ------------ .. warning:: - For Python3.11+ users: Many of the required scientific libraries do not currently have wheels that support the latest - python. In order to ensure that installation of xclim doesn't fail, we suggest installing the `Cython` module - before installing xclim in order to compile necessary libraries from source packages. + While `xclim` strives to be compatible with latest releases and development versions of upstream libraries, many of the required base libraries (`numpy`, `scipy`, `numba`, etc.) may lag by several months before supporting the latest minor releases of Python. + + In order to ensure that installation of `xclim` doesn't fail, we suggest installing the `Cython` module before installing `xclim` in order to compile necessary libraries from their source packages, if required. The sources for xclim can be downloaded from the `Github repo`_. diff --git a/docs/logos/empty.png b/docs/logos/empty.png new file mode 100644 index 000000000..fd9b503f8 Binary files /dev/null and b/docs/logos/empty.png differ diff --git a/docs/logos/xclim-logo-dark.png b/docs/logos/xclim-logo-dark.png new file mode 100644 index 000000000..f28ea80a3 Binary files /dev/null and b/docs/logos/xclim-logo-dark.png differ diff --git a/docs/logos/xclim-logo.png b/docs/logos/xclim-logo-light.png similarity index 100% rename from docs/logos/xclim-logo.png rename to docs/logos/xclim-logo-light.png diff --git a/docs/logos/xclim-logo-small-dark.png b/docs/logos/xclim-logo-small-dark.png new file mode 100644 index 000000000..fadaa2a76 Binary files /dev/null and b/docs/logos/xclim-logo-small-dark.png differ diff --git a/docs/logos/xclim-logo-small-light.png b/docs/logos/xclim-logo-small-light.png new file mode 100644 index 000000000..191b0178d Binary files /dev/null and b/docs/logos/xclim-logo-small-light.png differ diff --git a/docs/logos/xclim-logo-small.png b/docs/logos/xclim-logo-small.png deleted file mode 100644 index 615b9271e..000000000 Binary files a/docs/logos/xclim-logo-small.png and /dev/null differ diff --git a/docs/notebooks/Indicator.svg b/docs/notebooks/Indicator.svg index 742b663f3..619cb7c16 100644 --- a/docs/notebooks/Indicator.svg +++ b/docs/notebooks/Indicator.svg @@ -160,7 +160,9 @@ - + + + diff --git a/docs/notebooks/example.ipynb b/docs/notebooks/example.ipynb index 26d8f0b9b..cb237befd 100644 --- a/docs/notebooks/example.ipynb +++ b/docs/notebooks/example.ipynb @@ -138,7 +138,7 @@ " chunks={\"time\": 365, \"lat\": 168, \"lon\": 150},\n", " drop_variables=[\"ts\", \"time_vectors\"],\n", ")\n", - "print(ds)" + "ds" ] }, { @@ -195,7 +195,7 @@ "outputs": [], "source": [ "ds2 = ds.sel(lat=slice(50, 45), lon=slice(-70, -65), time=slice(\"2090\", \"2100\"))\n", - "print(ds2.tasmin)" + "ds2.tasmin" ] }, { @@ -205,7 +205,7 @@ "outputs": [], "source": [ "ds3 = ds.sel(lat=46.8, lon=-71.22, method=\"nearest\").sel(time=\"1993\")\n", - "print(ds3.tasmin)" + "ds3.tasmin" ] }, { @@ -259,7 +259,7 @@ "outputs": [], "source": [ "out = xclim.atmos.tx_max(ds2.tasmax, freq=\"YS\")\n", - "print(out)" + "out" ] }, { @@ -294,7 +294,7 @@ "outputs": [], "source": [ "out = xclim.indices.tx_days_above(ds2.tasmax, thresh=\"30 degC\", freq=\"YS\")\n", - "print(out)" + "out" ] }, { @@ -315,7 +315,7 @@ "outputs": [], "source": [ "out = xclim.atmos.tx_days_above(ds2.tasmax, thresh=\"30 degC\", freq=\"YS\")\n", - "print(out)" + "out" ] }, { @@ -331,7 +331,7 @@ "\n", "# Add our climate index as a data variable to the dataset\n", "ds_out[out.name] = out\n", - "print(ds_out)" + "ds_out" ] }, { @@ -462,7 +462,7 @@ "source": [ "%%time\n", "output_file = output_folder / \"test_tx_max.nc\"\n", - "dsOut.to_netcdf(output_file)" + "ds_out.to_netcdf(output_file)" ] }, { diff --git a/docs/notebooks/example/example.py b/docs/notebooks/example/example.py index 18b6be06f..e5cee3ad4 100644 --- a/docs/notebooks/example/example.py +++ b/docs/notebooks/example/example.py @@ -9,7 +9,7 @@ @declare_units(pr="[precipitation]") def extreme_precip_accumulation_and_days( pr: xr.DataArray, perc: float = 95, freq: str = "YS" -): +) -> tuple[xr.DataArray, xr.DataArray]: """Total precipitation accumulation during extreme events and number of days of such precipitation. The `perc` percentile of the precipitation (including all values, not in a day-of-year manner) @@ -19,18 +19,18 @@ def extreme_precip_accumulation_and_days( Parameters ---------- pr: xr.DataArray - Precipitation flux (both phases). + Precipitation flux (both phases). perc: float - Percentile corresponding to "extreme" precipitation, [0-100]. + Percentile corresponding to "extreme" precipitation, [0-100]. freq: str - Resampling frequency. + Resampling frequency. Returns ------- xarray.DataArray - Precipitation accumulated during events where pr was above the {perc}th percentile of the whole series. + Precipitation accumulated during events where pr was above the {perc}th percentile of the whole series. xarray.DataArray - Number of days where pr was above the {perc}th percentile of the whole series. + Number of days where pr was above the {perc}th percentile of the whole series. """ pr_thresh = pr.quantile(perc / 100, dim="time").drop_vars("quantile") @@ -38,8 +38,8 @@ def extreme_precip_accumulation_and_days( pr_extreme = rate2amount(pr).where(extreme_days) out1 = pr_extreme.resample(time=freq).sum() - out1.attrs["units"] = pr_extreme.units + out1 = out1.assign_attrs(units=pr_extreme.units) out2 = extreme_days.resample(time=freq).sum() - out2.attrs["units"] = "days" + out2 = out2.assign_attrs(units="days") return out1, out2 diff --git a/docs/notebooks/extendxclim.ipynb b/docs/notebooks/extendxclim.ipynb index 7e190eb36..aa2b679c9 100644 --- a/docs/notebooks/extendxclim.ipynb +++ b/docs/notebooks/extendxclim.ipynb @@ -37,7 +37,6 @@ "\n", "This introduction will focus on the Indicator/Index part of `xclim` and how one can extend it by implementing new ones.\n", "\n", - "\n", "## Indices vs Indicators\n", "\n", "Internally and in the documentation, `xclim` makes a distinction between \"indices\" and \"indicators\".\n", @@ -96,6 +95,7 @@ "from __future__ import annotations\n", "\n", "import xarray as xr\n", + "from IPython.display import Code, display\n", "\n", "import xclim\n", "from xclim.core.units import convert_units_to, declare_units\n", @@ -106,7 +106,7 @@ "def tx_days_compare(\n", " tasmax: xr.DataArray, thresh: str = \"0 degC\", op: str = \">\", freq: str = \"YS\"\n", "):\n", - " r\"\"\"Number of days where maximum daily temperature. is above or under a threshold.\n", + " r\"\"\"Number of days where maximum daily temperature is above or under a threshold.\n", "\n", " The daily maximum temperature is compared to a threshold using a given operator and the number\n", " of days where the condition is true is returned.\n", @@ -116,19 +116,19 @@ " Parameters\n", " ----------\n", " tasmax : xarray.DataArray\n", - " Maximum daily temperature.\n", + " Maximum daily temperature.\n", " thresh : str\n", - " Threshold temperature to compare to.\n", + " Threshold temperature to compare to.\n", " op : {'>', '<'}\n", - " The operator to use.\n", - " # A fixed set of choices can be imposed. Only strings, numbers, booleans or None are accepted.\n", + " The operator to use.\n", + " # A fixed set of choices can be imposed. Only strings, numbers, booleans or None are accepted.\n", " freq : str\n", - " Resampling frequency.\n", + " Resampling frequency.\n", "\n", " Returns\n", " -------\n", " xarray.DataArray, [temperature]\n", - " Maximum value of daily maximum temperature.\n", + " Maximum value of daily maximum temperature.\n", "\n", " Notes\n", " -----\n", @@ -236,7 +236,7 @@ "metadata": {}, "outputs": [], "source": [ - "print(tg_mean_c.__doc__)" + "display(Code(tg_mean_c.__doc__, language=\"rst\"))" ] }, { @@ -325,14 +325,6 @@ }, "outputs": [], "source": [ - "from pathlib import Path\n", - "\n", - "from pygments import highlight\n", - "\n", - "# Workaround absence of syntax highlighting in notebooks\n", - "from pygments.formatters import Terminal256Formatter\n", - "from pygments.lexers import JsonLexer, PythonLexer, YamlLexer\n", - "\n", "example_dir = notebook_folder / \"example\"\n", "\n", "with open(example_dir / \"example.py\") as f:\n", @@ -342,11 +334,7 @@ " ymldata = f.read()\n", "\n", "with open(example_dir / \"example.fr.json\") as f:\n", - " jsondata = f.read()\n", - "\n", - "highlighted_py = highlight(pydata, PythonLexer(), Terminal256Formatter(style=\"manni\"))\n", - "highlighted_yaml = highlight(ymldata, YamlLexer(), Terminal256Formatter(style=\"manni\"))\n", - "highlighted_json = highlight(jsondata, JsonLexer(), Terminal256Formatter(style=\"manni\"))" + " jsondata = f.read()" ] }, { @@ -357,11 +345,11 @@ "source": [ "# These variables were generated by a hidden cell above that syntax-colored them.\n", "print(\"Content of example.py :\")\n", - "print(highlighted_py)\n", + "display(Code(pydata, language=\"python\"))\n", "print(\"\\n\\nContent of example.yml :\")\n", - "print(highlighted_yaml)\n", + "display(Code(ymldata, language=\"yaml\"))\n", "print(\"\\n\\nContent of example.fr.json :\")\n", - "print(highlighted_json)" + "display(Code(jsondata, language=\"json\"))" ] }, { @@ -414,14 +402,17 @@ "metadata": {}, "outputs": [], "source": [ - "from importlib.resources import path\n", + "from importlib.resources import files\n", "\n", "import yamale\n", "\n", - "with path(\"xclim.data\", \"schema.yml\") as f:\n", + "data = files(\"xclim.data\").joinpath(\"schema.yml\")\n", + "with data as f:\n", " schema = yamale.make_schema(f)\n", - " data = yamale.make_data(example_dir / \"example.yml\") # in the example folder\n", - "yamale.validate(schema, data)" + "\n", + "example_module = yamale.make_data(example_dir / \"example.yml\") # in the example folder\n", + "\n", + "yamale.validate(schema, example_module)" ] }, { @@ -458,9 +449,8 @@ "metadata": {}, "outputs": [], "source": [ - "print(example.__doc__)\n", - "print(\"--\")\n", - "print(xc.indicators.example.R99p.__doc__)" + "docstring = f\"{example.__doc__}\\n---\\n\\n{xc.indicators.example.R99p.__doc__}\"\n", + "display(Code(docstring, language=\"rst\"))" ] }, { @@ -490,17 +480,20 @@ "\n", "outs = []\n", "with xc.set_options(metadata_locales=\"fr\"):\n", + " inds = [\"Indicators:\"]\n", " for name, ind in example.iter_indicators():\n", - " print(f\"Indicator: {name}\")\n", - " print(f\"\\tIdentifier: {ind.identifier}\")\n", + " inds.append(f\" {name}:\")\n", + " inds.append(f\" identifier: {ind.identifier}\")\n", " out = ind(ds=ds2) # Use all default arguments and variables from the dataset\n", " if isinstance(out, tuple):\n", " outs.extend(out)\n", - " for o in out:\n", - " print(f\"\\tLong name ({o.name}): {o.long_name}\")\n", + " for i, o in enumerate(out):\n", + " inds.append(f\" long_name_{i}: ({o.name}) {o.long_name}\")\n", " else:\n", " outs.append(out)\n", - " print(f\"\\tLong name ({out.name}): {out.long_name}\")" + " inds.append(f\" long_name: ({out.name}) {out.long_name}\")\n", + "\n", + "display(Code(\"\\n\".join(inds), language=\"yaml\"))" ] }, { diff --git a/environment.yml b/environment.yml index 6fd159c02..4db610902 100644 --- a/environment.yml +++ b/environment.yml @@ -29,7 +29,7 @@ dependencies: - flox - lmoments3 # Required for some Jupyter notebooks # Testing and development dependencies - - black ==24.1.1 + - black ==24.4.1 - blackdoc ==0.3.9 - bump-my-version >=0.17.1 - cairosvg @@ -40,6 +40,7 @@ dependencies: - flake8 - flake8-rst-docstrings - flit + - furo >=2023.9.10 - h5netcdf - ipykernel - ipython @@ -63,17 +64,18 @@ dependencies: - pytest-cov - pytest-socket - pytest-xdist >=3.2 - - ruff >=0.2.0 + - ruff >=0.3.0 - sphinx + - sphinx-autobuild >=2024.4.16 - sphinx-autodoc-typehints - sphinx-codeautolink - sphinx-copybutton - sphinx-mdinclude - - sphinx-rtd-theme >=1.0 - sphinxcontrib-bibtex - tokenize-rt - tox >=4.0 # - tox-conda # Will be added when a tox@v4.0+ compatible plugin is released. + - vulture # ==2.11 # The conda-forge version is out of date. - xdoctest - yamllint - pip diff --git a/pyproject.toml b/pyproject.toml index ade92e544..028644442 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ dependencies = [ [project.optional-dependencies] dev = [ # Dev tools and testing - "black ==24.4.2", + "black[jupyter] ==24.4.2", "blackdoc ==0.3.9", "bump-my-version >=0.17.1", "codespell", @@ -80,27 +80,28 @@ dev = [ "pytest-cov", "pytest-socket", "pytest-xdist[psutil] >=3.2", - "ruff >=0.2.0", + "ruff >=0.3.0", "tokenize-rt", - "tox >=4.0", + "tox >=4.5", # "tox-conda", # Will be added when a tox@v4.0+ compatible plugin is released. "tox-gh >=1.3.1", - "vulture", + "vulture ==2.11", "xdoctest", - "yamllint", + "yamllint==1.35.1", # Documentation and examples "distributed >=2.0", + "furo >=2023.9.10", "ipykernel", "matplotlib", "nbsphinx", "nc-time-axis", "pooch", "sphinx", + "sphinx-autobuild >=2024.4.16", "sphinx-autodoc-typehints", "sphinx-codeautolink", "sphinx-copybutton", "sphinx-mdinclude", - "sphinx-rtd-theme >=1.0", "sphinxcontrib-bibtex", "sphinxcontrib-svg2pdfconverter[Cairosvg]" ] diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 56cd60efb..98ffb2563 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -31,10 +31,10 @@ def test_indicator_docstring(): == "Based on indice :py:func:`~xclim.indices._multivariate.heat_wave_frequency`." ) assert doc[6] == "Keywords : temperature health,." - assert doc[12] == " Default : `ds.tasmin`. [Required units : [temperature]]" + assert doc[12] == " Default : `ds.tasmin`. [Required units : [temperature]]" assert ( doc[41] - == " Total number of series of at least {window} consecutive days with daily minimum temperature above " + == " Total number of series of at least {window} consecutive days with daily minimum temperature above " "{thresh_tasmin} and daily maximum temperature above {thresh_tasmax} (heat_wave_events), " "with additional attributes: **description**: {freq} number of heat wave events within a given period. " "A heat wave occurs when daily minimum and maximum temperatures exceed {thresh_tasmin} and {thresh_tasmax}, " @@ -42,7 +42,7 @@ def test_indicator_docstring(): ) doc = degree_days_exceedance_date.__doc__.split("\n") - assert doc[21] == " Default : >. " + assert doc[21] == " Default : >. " def test_update_xclim_history(atmosds): diff --git a/tox.ini b/tox.ini index cd55e0533..5bdf4d89e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -min_version = 4.0 +min_version = 4.5 env_list = lint docs @@ -32,18 +32,18 @@ deps = flake8 flake8-alphabetize flake8-rst-docstrings - black[jupyter]==24.1.0 + black[jupyter]==24.4.1 blackdoc==0.3.9 isort==5.13.2 nbqa - ruff>=0.1.0 - vulture - yamllint + ruff==0.3.0 + vulture==2.11 + yamllint==1.35.1 commands_pre = commands = black --check xclim tests isort --check xclim tests - ruff xclim tests + ruff check xclim tests flake8 --config=.flake8 xclim tests vulture xclim tests nbqa black --check docs diff --git a/xclim/core/formatting.py b/xclim/core/formatting.py index ff443dcbb..8630fcba6 100644 --- a/xclim/core/formatting.py +++ b/xclim/core/formatting.py @@ -14,7 +14,7 @@ from collections.abc import Sequence from fnmatch import fnmatch from inspect import _empty, signature # noqa -from typing import Any +from typing import Any, Callable import xarray as xr from boltons.funcutils import wraps @@ -307,7 +307,7 @@ def merge_attributes( new_line: str = "\n", missing_str: str | None = None, **inputs_kws: xr.DataArray | xr.Dataset, -): +) -> str: r"""Merge attributes from several DataArrays or Datasets. If more than one input is given, its name (if available) is prepended as: " : ". @@ -359,7 +359,7 @@ def update_history( *inputs_list: xr.DataArray | xr.Dataset, new_name: str | None = None, **inputs_kws: xr.DataArray | xr.Dataset, -): +) -> str: r"""Return a history string with the timestamped message and the combination of the history of all inputs. The new history entry is formatted as "[] : - xclim version: ." @@ -406,7 +406,7 @@ def update_history( return merged_history -def update_xclim_history(func): +def update_xclim_history(func: Callable): """Decorator that auto-generates and fills the history attribute. The history is generated from the signature of the function and added to the first output. @@ -449,8 +449,8 @@ def _call_and_add_history(*args, **kwargs): return _call_and_add_history -def gen_call_string(funcname: str, *args, **kwargs): - """Generate a signature string for use in the history attribute. +def gen_call_string(funcname: str, *args, **kwargs) -> str: + r"""Generate a signature string for use in the history attribute. DataArrays and Dataset are replaced with their name, while Nones, floats, ints and strings are printed directly. All other objects have their type printed between < >. @@ -462,7 +462,7 @@ def gen_call_string(funcname: str, *args, **kwargs): ---------- funcname : str Name of the function - args, kwargs + \*args, \*\*kwargs Arguments given to the function. Example @@ -491,7 +491,7 @@ def gen_call_string(funcname: str, *args, **kwargs): return f"{funcname}({', '.join(elements)})" -def prefix_attrs(source: dict, keys: Sequence, prefix: str): +def prefix_attrs(source: dict, keys: Sequence, prefix: str) -> dict: """Rename some keys of a dictionary by adding a prefix. Parameters @@ -517,7 +517,7 @@ def prefix_attrs(source: dict, keys: Sequence, prefix: str): return out -def unprefix_attrs(source: dict, keys: Sequence, prefix: str): +def unprefix_attrs(source: dict, keys: Sequence, prefix: str) -> dict: """Remove prefix from keys in a dictionary. Parameters @@ -562,7 +562,9 @@ def unprefix_attrs(source: dict, keys: Sequence, prefix: str): } -def _gen_parameters_section(parameters: dict, allowed_periods: list[str] | None = None): +def _gen_parameters_section( + parameters: dict[str, dict[str, Any]], allowed_periods: list[str] | None = None +) -> str: """Generate the "parameters" section of the indicator docstring. Parameters @@ -571,6 +573,10 @@ def _gen_parameters_section(parameters: dict, allowed_periods: list[str] | None Parameters dictionary (`Ind.parameters`). allowed_periods : list of str, optional Restrict parameters to specific periods. Default: None. + + Returns + ------- + str """ section = "Parameters\n----------\n" for name, param in parameters.items(): @@ -600,22 +606,28 @@ def _gen_parameters_section(parameters: dict, allowed_periods: list[str] | None unitstr = f"[Required units : {param.units}]" else: unitstr = "" - section += f"{name} : {annotstr}\n {desc_str}\n {defstr}{unitstr}\n" + section += f"{name} {': ' if annotstr else ''}{annotstr}\n {desc_str}\n {defstr}{unitstr}\n" return section -def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]): +def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]) -> str: """Generate the "Returns" section of an indicator's docstring. Parameters ---------- cf_attrs : Sequence[Dict[str, Any]] - The list of attributes, usually Indicator.cf_attrs. + The list of attributes, usually Indicator.cf_attrs. + + Returns + ------- + str """ section = "Returns\n-------\n" for attrs in cf_attrs: + if not section.endswith("\n"): + section += "\n" section += f"{attrs['var_name']} : DataArray\n" - section += f" {attrs.get('long_name', '')}" + section += f" {attrs.get('long_name', '')}" if "standard_name" in attrs: section += f" ({attrs['standard_name']})" if "units" in attrs: @@ -627,7 +639,8 @@ def _gen_returns_section(cf_attrs: Sequence[dict[str, Any]]): attr = "" added_section += f" **{key}**: {attr};" if added_section: - section = f"{section}, with additional attributes:{added_section[:-1]}\n" + section = f"{section}, with additional attributes:{added_section[:-1]}" + section += "\n" return section diff --git a/xclim/core/indicator.py b/xclim/core/indicator.py index 54d25642f..40d68ecc4 100644 --- a/xclim/core/indicator.py +++ b/xclim/core/indicator.py @@ -1167,8 +1167,8 @@ def json(self, args=None): Parameters ---------- args : mapping, optional - Arguments as passed to the call method of the indicator. - If not given, the default arguments will be used when formatting the attributes. + Arguments as passed to the call method of the indicator. + If not given, the default arguments will be used when formatting the attributes. Notes ----- @@ -1213,11 +1213,11 @@ def _format( Parameters ---------- attrs : dict - Attributes containing tags to replace with arguments' values. + Attributes containing tags to replace with arguments' values. args : dict, optional - Function call arguments. If not given, the default arguments will be used when formatting the attributes. + Function call arguments. If not given, the default arguments will be used when formatting the attributes. formatter : AttrFormatter - Plaintext mappings for indicator attributes. + Plaintext mappings for indicator attributes. Returns ------- @@ -1385,10 +1385,12 @@ class CheckMissingIndicator(Indicator): Parameters ---------- missing : {any, wmo, pct, at_least_n, skip, from_context} - The name of the missing value method. See `xclim.core.missing.MissingBase` to create new custom methods. If - None, this will be determined by the global configuration (see `xclim.set_options`). Defaults to "from_context". + The name of the missing value method. See `xclim.core.missing.MissingBase` to create new custom methods. + If None, this will be determined by the global configuration (see `xclim.set_options`). + Defaults to "from_context". missing_options : dict, optional - Arguments to pass to the `missing` function. If None, this will be determined by the global configuration. + Arguments to pass to the `missing` function. + If None, this will be determined by the global configuration. """ missing = "from_context" @@ -1467,10 +1469,12 @@ class ReducingIndicator(CheckMissingIndicator): Parameters ---------- missing : {any, wmo, pct, at_least_n, skip, from_context} - The name of the missing value method. See `xclim.core.missing.MissingBase` to create new custom methods. If - None, this will be determined by the global configuration (see `xclim.set_options`). Defaults to "from_context". + The name of the missing value method. See `xclim.core.missing.MissingBase` to create new custom methods. + If None, this will be determined by the global configuration (see `xclim.set_options`). + Defaults to "from_context". missing_options : dict, optional - Arguments to pass to the `missing` function. If None, this will be determined by the global configuration. + Arguments to pass to the `missing` function. + If None, this will be determined by the global configuration. """ def _get_missing_freq(self, params): @@ -1487,14 +1491,16 @@ class ResamplingIndicator(CheckMissingIndicator): Parameters ---------- missing : {any, wmo, pct, at_least_n, skip, from_context} - The name of the missing value method. See `xclim.core.missing.MissingBase` to create new custom methods. If - None, this will be determined by the global configuration (see `xclim.set_options`). Defaults to "from_context". + The name of the missing value method. See `xclim.core.missing.MissingBase` to create new custom methods. + If None, this will be determined by the global configuration (see `xclim.set_options`). + Defaults to "from_context". missing_options : dict, optional - Arguments to pass to the `missing` function. If None, this will be determined by the global configuration. + Arguments to pass to the `missing` function. + If None, this will be determined by the global configuration. allowed_periods : Sequence[str], optional - A list of allowed periods, i.e. base parts of the `freq` parameter. For example, indicators meant to be - computed annually only will have `allowed_periods=["A"]`. `None` means "any period" or that the - indicator doesn't take a `freq` argument. + A list of allowed periods, i.e. base parts of the `freq` parameter. + For example, indicators meant to be computed annually only will have `allowed_periods=["A"]`. + `None` means "any period" or that the indicator doesn't take a `freq` argument. """ allowed_periods: list[str] | None = None @@ -1611,12 +1617,14 @@ def build_indicator_module( Parameters ---------- name : str - New module name. If it already exists, the module is extended with the passed objects, - overwriting those with same names. + New module name. + If it already exists, the module is extended with the passed objects, overwriting those with same names. objs : dict[str, Indicator] - Mapping of the indicators to put in the new module. Keyed by the name they will take in that module. + Mapping of the indicators to put in the new module. + Keyed by the name they will take in that module. doc : str - Docstring of the new module. Defaults to a simple header. Invalid if the module already exists. + Docstring of the new module. Defaults to a simple header. + Invalid if the module already exists. reload : bool If reload is True and the module already exists, it is first removed before being rebuilt. If False (default), indicators are added or updated, but not removed. @@ -1812,7 +1820,8 @@ def _merge_attrs(dbase, dextra, attr, sep): for varname, vardata in yml.get("variables", {}).items(): if varname in VARIABLES and VARIABLES[varname] != vardata: warnings.warn( - f"Variable {varname} from module {module_name} will overwrite the one already defined in `xclim.core.utils.VARIABLES`" + f"Variable {varname} from module {module_name} " + "will overwrite the one already defined in `xclim.core.utils.VARIABLES`" ) VARIABLES[varname] = vardata.copy() diff --git a/xclim/core/locales.py b/xclim/core/locales.py index b0be2eef5..a813e463d 100644 --- a/xclim/core/locales.py +++ b/xclim/core/locales.py @@ -6,8 +6,8 @@ climate indicators computed by xclim. Go to :ref:`notebooks/customize:Adding translated metadata` to see how to use this feature. -All the methods and objects in this module use localization data given in json files. -These files are expected to be defined as in this example for french: +All the methods and objects in this module use localization data given in JSON files. +These files are expected to be defined as in this example for French: .. code-block:: @@ -34,14 +34,15 @@ Use the `ind.__class__.__name__` accessor to get its registry name. Here, the usual parameter passed to the formatting of "description" is "freq" and is usually translated from "YS" -to "annual". However, in french and in this sentence, the feminine form should be used, so the "f" modifier is added +to "annual". However, in French and in this sentence, the feminine form should be used, so the "f" modifier is added by the translator so that the formatting function knows which translation to use. Acceptable entries for the mappings are limited to what is already defined in `xclim.core.indicators.utils.default_formatter`. For user-provided internationalization dictionaries, only the "attrs_mapping" and its "modifiers" key are mandatory, all other entries (translations of frequent parameters and all indicator entries) are optional. For xclim-provided translations (for now only French), all indicators must have en entry and the "attrs_mapping" -entries must match exactly the default formatter. Those default translations are found in the `xclim/locales` folder. +entries must match exactly the default formatter. +Those default translations are found in the `xclim/locales` folder. """ from __future__ import annotations @@ -186,7 +187,8 @@ def get_local_attrs( local_attrs.update(loc_dict.get(other_ind, {})) if not local_attrs: warnings.warn( - f"Attributes of indicator {', '.join(indicator)} in language {locale} were requested, but none were found." + f"Attributes of indicator {', '.join(indicator)} in language {locale} " + "were requested, but none were found." ) else: for name in TRANSLATABLE_ATTRS: diff --git a/xclim/data/__init__.py b/xclim/data/__init__.py index c767fc157..380b73fae 100644 --- a/xclim/data/__init__.py +++ b/xclim/data/__init__.py @@ -1 +1,22 @@ -"""JSON and YAML definitions for virtual modules and internationalisation support.""" +""" +==================== +Data files for xclim +==================== + +JSON and YAML definitions for virtual modules and internationalisation support. + +Currently, the following virtual modules are defined: + * ANUCLIM (`anuclim.yml`) + * CF (`cf.yml`) + * ICCLIM (`icclim.yml`) + +And the following languages are supported: + * English (default) + * French (`fr.json`) + +These files are used by xclim to define new indicators and to provide translations for the user interface. + +Additionally, this package contains the following data files: + * `schema.yml`: YAML schema for detailing class definitions used for indicators. + * `variables.yml`: YAML schema defining the variables and their metadata used in the indicator definitions. +"""