diff --git a/.github/workflows/ci-linkchecks.yml b/.github/workflows/ci-linkchecks.yml new file mode 100644 index 0000000000..edf1c048cf --- /dev/null +++ b/.github/workflows/ci-linkchecks.yml @@ -0,0 +1,34 @@ +name: Linkcheck + +on: + workflow_dispatch: + schedule: + - cron: "00 06 * * *" + +jobs: + linkChecker: + runs-on: ubuntu-latest + permissions: + issues: write # required for peter-evans/create-issue-from-file + steps: + - uses: actions/checkout@v4 + + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 + with: + fail: false + args: "--verbose './docs/**/*.rst' './docs/**/*.inc' './lib/**/*.py'" + + - name: Create Issue From File + if: steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@e8ef132d6df98ed982188e460ebb3b5d4ef3a9cd + with: + title: Link Checker Report + content-filepath: ./lychee/out.md + labels: "Bot, Type: Documentation, Type: Bug" + + - name: Fail Workflow On Link Errors + if: steps.lychee.outputs.exit_code != 0 + run: + exit {{ steps.lychee.outputs.exit_code }} diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 069ea7c980..c97858a7ef 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -36,7 +36,7 @@ jobs: matrix: os: ["ubuntu-latest"] python-version: ["3.13"] - session: ["doctest", "gallery", "linkcheck"] + session: ["doctest", "gallery"] include: - os: "ubuntu-latest" python-version: "3.13" diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 0000000000..0897ba6686 --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,52 @@ +# nonfunctional, found in some code examples +file:/// + +# DEAD : legacy in various old whatsnews +https://biggus.readthedocs.io + +# unkown problem, works in browser : used in further_topics/ugrid/data_model +https://doi.org/10.3390/jmse2010194 + +# DEAD, todo:remove, used in docs/src/userguide/plotting_a_cube.rst +https://effbot.org + +# nonfunctional, found in some code examples +https://foo/ + +# DEAD, todo:remove, used in docs/src/further_topics/ugrid/data_model.rst +https://ibm-design-language.eu-de.mybluemix.net/design/language/resources/color-library + +# DEAD, legacy in whatsnew/1.4.rst +https://geoport.whoi.edu/thredds/dodsC/bathy/gom15 + +# exist, but bad certificates +https://georepository.com +https://geoport.whoi.edu:80/thredds/ + +# catch (at least) github userids, of which many in whatsnews, too many --> "too many requests" failures +https://github.com/[^/]*$ + +# nonfunctional example, used in docs/src/developers_guide/gitwash/development_workflow.rst +https://github.com/your-user-name/iris + +# problem with bad certificate (review sometime?) +https://scitools.github.com/cartopy + +# legacy ref in whatsnew/3.0.rst +https://stickler-ci.com + +# DEAD, todo:remove, used in lib/iris/symbols.py +https://www.wmo.int/pages/prog/www/DPFS/documents/485_Vol_I_en_colour.pdf + +# DEAD, todo:remove, used in docs/src/userguide/plotting_a_cube.rst +# unkown problem, works in browser : used in docs/src/index.rst +https://www.flaticon.com + +# nonfunctional example, used in lib/iris/io/__init__.py +https://www.thing.com + +# DEAD, todo:remove, used in docs/src/userguide/plotting_a_cube.rst +https://www.personal.psu.edu/cab38/ColorBrewer/ColorBrewer_updates.html + +# nonfunctional, found in some code examples +https://www.somehost.com diff --git a/MANIFEST.in b/MANIFEST.in index e594303d8f..da9a68413c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,6 +11,7 @@ exclude .git-blame-ignore-revs exclude .git_archival.txt exclude .gitattributes exclude .gitignore +exclude .lycheeignore exclude .mailmap exclude .pre-commit-config.yaml exclude .readthedocs.yml diff --git a/README.md b/README.md index 7eec86c6da..56a4e954af 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ |------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ⚙️ CI | [![ci-manifest](https://github.com/SciTools/iris/actions/workflows/ci-manifest.yml/badge.svg)](https://github.com/SciTools/iris/actions/workflows/ci-manifest.yml) [![ci-tests](https://github.com/SciTools/iris/actions/workflows/ci-tests.yml/badge.svg)](https://github.com/SciTools/iris/actions/workflows/ci-tests.yml) [![ci-wheels](https://github.com/SciTools/iris/actions/workflows/ci-wheels.yml/badge.svg)](https://github.com/SciTools/iris/actions/workflows/ci-wheels.yml) [![pre-commit](https://results.pre-commit.ci/badge/github/SciTools/iris/main.svg)](https://results.pre-commit.ci/latest/github/SciTools/iris/main) | | 💬 Community | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-2.1-4baaaa.svg)](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) [![GH Discussions](https://img.shields.io/badge/github-discussions%20%F0%9F%92%AC-yellow?logo=github&logoColor=lightgrey)](https://github.com/SciTools/iris/discussions) [![twitter](https://img.shields.io/twitter/follow/scitools_iris?color=yellow&label=twitter%7Cscitools_iris&logo=twitter&style=plastic)](https://twitter.com/scitools_iris) | -| 📖 Documentation | [![rtd](https://readthedocs.org/projects/scitools-iris/badge/?version=latest)](https://scitools-iris.readthedocs.io/en/latest/?badge=latest) | +| 📖 Documentation | [![rtd](https://readthedocs.org/projects/scitools-iris/badge/?version=latest)](https://scitools-iris.readthedocs.io/en/latest/?badge=latest) [![Check Links](https://github.com/SciTools/iris/actions/workflows/ci-linkchecks.yml/badge.svg)](https://github.com/SciTools/iris/actions/workflows/ci-linkchecks.yml) | | 📈 Health | [![codecov](https://codecov.io/gh/SciTools/iris/branch/main/graph/badge.svg?token=0GeICSIF3g)](https://codecov.io/gh/SciTools/iris) | | ✨ Meta | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![NEP29](https://raster.shields.io/badge/follows-NEP29-orange.png)](https://numpy.org/neps/nep-0029-deprecation_policy.html) [![license - bds-3-clause](https://img.shields.io/github/license/SciTools/iris)](https://github.com/SciTools/iris/blob/main/LICENSE) [![conda platform](https://img.shields.io/conda/pn/conda-forge/iris.svg)](https://anaconda.org/conda-forge/iris) | | 📦 Package | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.595182.svg)](https://doi.org/10.5281/zenodo.595182) [![conda-forge](https://img.shields.io/conda/vn/conda-forge/iris?color=orange&label=conda-forge&logo=conda-forge&logoColor=white)](https://anaconda.org/conda-forge/iris) [![pypi](https://img.shields.io/pypi/v/scitools-iris?color=orange&label=pypi&logo=python&logoColor=white)](https://pypi.org/project/scitools-iris/) [![pypi - python version](https://img.shields.io/pypi/pyversions/scitools-iris.svg?color=orange&logo=python&label=python&logoColor=white)](https://pypi.org/project/scitools-iris/) | diff --git a/docs/src/further_topics/dask_best_practices/index.rst b/docs/src/further_topics/dask_best_practices/index.rst index f126427d3f..9148069198 100644 --- a/docs/src/further_topics/dask_best_practices/index.rst +++ b/docs/src/further_topics/dask_best_practices/index.rst @@ -44,13 +44,13 @@ CPU available. This interacts badly with Dask: * NumPy will generate enough threads to use all available CPUs even if Dask is deliberately configured to only use a subset of CPUs. The resulting sharing of CPUs between threads greatly reduces performance. -* `Dask is already designed to parallelise with NumPy arrays `_, so adding NumPy's 'competing' layer of - parallelisation could cause unpredictable performance. +* `Dask is already designed to parallelise with NumPy arrays + `_, so adding NumPy's 'competing' layer + of parallelisation could cause unpredictable performance. Therefore it is best to prevent NumPy performing its own parallelisation, `a -suggestion made in Dask's own documentation `_. +suggestion made in Dask's own documentation +`_. The following commands will ensure this in all scenarios: in Python... @@ -158,8 +158,8 @@ NetCDF Files NetCDF files can include their own chunking specification. This is either specified when creating the file, or is automatically assigned if one or -more of the dimensions is `unlimited `_. +more of the dimensions is +`unlimited `_. Importantly, netCDF chunk shapes are **not optimised for Dask performance**. diff --git a/docs/src/userguide/glossary.rst b/docs/src/userguide/glossary.rst index 6ab93125bd..b78bce1ea4 100644 --- a/docs/src/userguide/glossary.rst +++ b/docs/src/userguide/glossary.rst @@ -188,7 +188,7 @@ Glossary Standard Name A name describing a :term:`phenomenon`, one from a fixed list - defined at `CF Standard Names `_. + defined at `CF Standard Names `_. | **Related:** :term:`Long Name` **|** :term:`Cube` | **More information:** :doc:`iris_cubes` diff --git a/docs/src/whatsnew/2.3.rst b/docs/src/whatsnew/2.3.rst index bec45a6603..66c6784568 100644 --- a/docs/src/whatsnew/2.3.rst +++ b/docs/src/whatsnew/2.3.rst @@ -29,8 +29,7 @@ Features .. admonition:: Climatological Coordinate Support Iris can now load, store and save `NetCDF climatological coordinates - `_. Any cube time + `_. Any cube time coordinate can be marked as a climatological time axis using the boolean property: ``climatological``. The climatological bounds are stored in the coordinate's ``bounds`` property. @@ -41,9 +40,8 @@ Features coordinate's ``bounds`` property are written to a NetCDF boundary variable called '_bounds'. These are in place of a standard 'bounds' attribute and accompanying boundary variable. See below - for an `example adapted from CF conventions `_: + for an + `example adapted from CF conventions `_: .. code-block:: none @@ -116,8 +114,7 @@ Features * New coordinate system: :class:`iris.coord_systems.Geostationary`, including load and save support, based on the `CF Geostationary projection - definition `_. + definition `_. * :class:`iris.coord_systems.VerticalPerspective` can now be saved to and loaded from NetCDF files. @@ -164,8 +161,7 @@ Bugs Fixed points. * :class:`iris.coord_systems.VerticalPerspective` coordinate system now uses - the `CF Vertical perspective definition `_; had been + the `CF Vertical perspective definition `_; had been erroneously using Geostationary. * :class:`~iris.coords.CellMethod` will now only use valid @@ -227,8 +223,7 @@ Internal ======== * Iris now supports Proj4 up to version 5, but not yet 6 or beyond, pending - `fixes to some cartopy tests `_. + `fixes to some cartopy tests `_. * Iris now requires Dask >= 1.2 to allow for improved coordinate equality checks. @@ -238,7 +233,7 @@ Documentation ============= * Adopted a - `new colour logo for Iris `_ + `new colour logo for Iris `_ * Added a gallery example showing how to concatenate NEMO ocean model data, see :ref:`sphx_glr_generated_gallery_oceanography_plot_load_nemo.py`. diff --git a/docs/src/why_iris.rst b/docs/src/why_iris.rst index 6c9b5fb7fb..19832ce2d4 100644 --- a/docs/src/why_iris.rst +++ b/docs/src/why_iris.rst @@ -11,7 +11,7 @@ giving you a powerful, format-agnostic interface for working with your data. It excels when working with multi-dimensional Earth Science data, where tabular representations become unwieldy and inefficient. -`CF Standard names `_, +`CF Standard names `_, `units `_, and coordinate metadata are built into Iris, giving you a rich and expressive interface for maintaining an accurate representation of your data. Its treatment of data and diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index bfbc75507e..55ad4e1319 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -46,7 +46,7 @@ ] -# https://www.unidata.ucar.edu/software/netcdf/docs/netcdf_data_set_components.html#object_name +# https://docs.unidata.ucar.edu/nug/current/netcdf_data_set_components.html#object_name _TOKEN_PARSE = re.compile(r"""^[a-zA-Z0-9][\w\.\+\-@]*$""") diff --git a/lib/iris/experimental/raster.py b/lib/iris/experimental/raster.py index 52ef2f651b..7fc9c5153c 100644 --- a/lib/iris/experimental/raster.py +++ b/lib/iris/experimental/raster.py @@ -118,7 +118,7 @@ def export_geotiff(cube, fname): Notes ----- For more details on GeoTiff specification and PixelIsArea, see: - https://www.remotesensing.org/geotiff/spec/geotiff2.5.html#2.5.2.2 + https://docs.ogc.org/is/19-008r4/19-008r4.html#_pixelisarea_raster_space .. deprecated:: 3.2.0 diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 6badeb6a0b..a195fe9d83 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -512,8 +512,8 @@ def write( example, if `least_significant_digit=1`, data will be quantized using `numpy.around(scale*data)/scale`, where `scale = 2**bits`, and `bits` is determined so that a precision of 0.1 is retained (in - this case `bits=4`). From - `here `__: + this case `bits=4`). From the + `metadata conventions `__: "least_significant_digit -- power of ten of the smallest decimal place in unpacked data that is a reliable value". Default is `None`, or no quantization, or 'lossless' compression. @@ -2473,7 +2473,7 @@ def save( Used to manually specify the HDF5 chunksizes for each dimension of the variable. A detailed discussion of HDF chunking and I/O performance is available - `here `__. + `here `__. Basically, you want the chunk size for each dimension to match as closely as possible the size of the data block that users will read from the file. `chunksizes` cannot be set if `contiguous=True`. @@ -2502,8 +2502,8 @@ def save( describes a numpy integer dtype (i.e. 'i2', 'short', 'u4') or a dict of packing parameters as described below or an iterable of such types, strings, or dicts. This provides support for netCDF data packing as - described in - `here `__ + described in the + `metadata conventions `__ If this argument is a type (or type string), appropriate values of scale_factor and add_offset will be automatically calculated based on `cube.data` and possible masking. For more control, pass a dict with diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index a718a10ec8..3c7e524e1e 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -103,7 +103,7 @@ def test_python_versions(): ci_tests_file, ( f'python-version: ["{latest_supported}"]\n' - f'{" " * 8}session: ["doctest", "gallery", "linkcheck"]' + f'{" " * 8}session: ["doctest", "gallery"]' ), ), (benchmark_runner_file, f'python_version = "{latest_supported}"'), diff --git a/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py b/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py index e8b7d817e1..827f968c71 100644 --- a/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py +++ b/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py @@ -110,7 +110,7 @@ def make_cube(self, calendar): def test_calendars(self): for calendar in calendars: # Skip the Julian calendar due to - # https://github.com/Unidata/netcdftime/issues/13 + # https://github.com/Unidata/cftime/issues/13 # Remove this if block once the issue is resolved. if calendar == "julian": continue diff --git a/noxfile.py b/noxfile.py index 3c692a3b9d..415e4fc3d5 100644 --- a/noxfile.py +++ b/noxfile.py @@ -240,32 +240,6 @@ def gallery(session: nox.sessions.Session): ) -@nox.session(python=_PY_VERSION_DOCSBUILD, venv_backend="conda") -def linkcheck(session: nox.sessions.Session): - """Perform iris doc link check. - - Parameters - ---------- - session : object - A `nox.sessions.Session` object. - - """ - prepare_venv(session) - session.install("--no-deps", "--editable", ".") - session.cd("docs") - session.run( - "make", - "clean", - "html", - external=True, - ) - session.run( - "make", - "linkcheck", - external=True, - ) - - @nox.session(python=PY_VER, venv_backend="conda") def wheel(session: nox.sessions.Session): """Perform iris local wheel install and import test.