.. include:: ../common_links.inc

v3.0.2 (27 May 2021)
********************

This document explains the changes made to Iris for this release
(:doc:`View all changes <index>`.)


.. dropdown:: :opticon:`alert` v3.0.2 Patches
   :container: + shadow
   :title: text-primary text-center font-weight-bold
   :body: bg-light
   :animate: fade-in
   :open:

   The patches included in this release include:

   🐛 **Bugs Fixed**

   #. `@jonseddon`_ handled a malformed ``um_stash_source`` CF variable attribute in
      a netCDF file rather than raising a ``ValueError``. (:pull:`4035`)

   #. `@rcomer`_ fixed :meth:`~iris.cube.Cube.intersection` for special cases
      where one cell's bounds align with the requested maximum and minimum, as
      reported in :issue:`3391`. (:pull:`4059`)

   #. `@bjlittle`_ resolved a regression in arithmetic behaviour between a coordinate
      and a cube which resulted in a ``NotYetImplementedError`` being raised, as reported
      in :issue:`4000`. This fix supports ``+``, ``-``, ``*``, and ``/`` operations
      between a coordinate and a cube, and for convenience additionally includes
      :meth:`iris.cube.Cube.__neg__` support. (:pull:`4159`)

   📚 **Documentation**

   #. `@bjlittle`_ updated the ``intersphinx_mapping`` and fixed documentation
      to use ``stable`` URLs for `matplotlib`_. (:pull:`4003`) [``pre-v3.1.0``]

   💼 **Internal**

   #. `@jamesp`_ updated a test to the latest numpy version (:pull:`3977`) [``pre-v3.1.0``]

   #. `@bjlittle`_ enabled `cirrus-ci`_ compute credits for non-draft pull-requests
      from collaborators targeting the Iris ``master`` branch. (:pull:`4007`)
      [``pre-v3.1.0``]

   #. `@bjlittle`_ added conditional task execution to `.cirrus.yml`_ to allow
      developers to easily disable `cirrus-ci`_ tasks. See
      :ref:`skipping Cirrus-CI tasks`. (:pull:`4019`) [``pre-v3.1.0``]

   #. `@pp-mo`_ adjusted the use of :func:`dask.array.from_array` in :func:`iris._lazy_data.as_lazy_data`,
      to avoid the dask 'test access'. This makes loading of netcdf files with a
      large number of variables significantly faster. (:pull:`4135`)

   #. `@pp-mo`_ reverted a change made previously in (:pull:`3659`) to
      :meth:`iris.fileformats.pp.PPDataProxy.__getitem__`. The check for empty slicings
      is no longer needed since (:pull:`4135`) was added. (:pull:`4141`)

   Note that, the above contributions labelled with ``pre-v3.1.0`` are part of the forthcoming
   Iris v3.1.0 release, but require to be included in this patch release.


.. dropdown:: :opticon:`alert` v3.0.1 Patches
   :container: + shadow
   :title: text-primary text-center font-weight-bold
   :body: bg-light
   :animate: fade-in

   The patches included in this release include:

   💼 **Internal**

   #. `@bjlittle`_ gracefully promote formula terms within :mod:`~iris.aux_factory` that have ``units`` of ``unknown``
      to ``units`` of ``1`` (dimensionless), where the formula term **must** have dimensionless ``units``. Without this
      graceful treatment of ``units`` the resulting :class:`~iris.cube.Cube` will **not** contain the expected auxiliary
      factory, and the associated derived coordinate will be missing. (:pull:`3965`)


.. dropdown:: :opticon:`report` v3.0.0 Release Highlights
   :container: + shadow
   :title: text-primary text-center font-weight-bold
   :body: bg-light
   :animate: fade-in

   The highlights for this major release of Iris include:

   * We've finally dropped support for ``Python 2``, so welcome to ``Iris 3``
     and ``Python 3``!
   * We've extended our coverage of the `CF Conventions and Metadata`_ by
     introducing support for `CF Ancillary Data`_ and `Quality Flags`_,
   * Lazy regridding is now available for several regridding schemes,
   * Managing and manipulating metadata within Iris is now easier and more
     consistent thanks to the introduction of a new common metadata API,
   * :ref:`Cube arithmetic <cube maths>` has been significantly improved with
     regards to extended broadcasting, auto-transposition and a more lenient
     behaviour towards handling metadata and coordinates,
   * Our :ref:`documentation <whatsnew 3.0.2 docs>` has been refreshed,
     restructured, revitalised and rehosted on `readthedocs`_,
   * It's now easier than ever to :ref:`install Iris <installing_iris>`
     as a user or a developer, and the newly revamped developers guide walks
     you though how you can :ref:`get involved <development_where_to_start>`
     and contribute to Iris,
   * Also, this is a major release of Iris, so please be aware of the
     :ref:`incompatible changes <whatsnew 3.0.2 changes>` and
     :ref:`deprecations <whatsnew 3.0.2 deprecations>`.

   And finally, get in touch with us on `GitHub`_ if you have any issues or
   feature requests for improving Iris. Enjoy!


📢 Announcements
================

#. Congratulations to `@bouweandela`_, `@jvegasbsc`_, and `@zklaus`_ who
   recently became Iris core developers. They bring a wealth of expertise to the
   team, and are using Iris to underpin `ESMValTool`_ - "*A community diagnostic
   and performance metrics tool for routine evaluation of Earth system models
   in CMIP*". Welcome aboard! 🎉

#. Congratulations also goes to `@jonseddon`_ who recently became an Iris core
   developer. We look forward to seeing more of your awesome contributions! 🎉


✨ Features
===========

#. `@MoseleyS`_ greatly enhanced  the :mod:`~iris.fileformats.nimrod`
   module to provide richer meta-data translation when loading ``Nimrod`` data
   into cubes. This covers most known operational use-cases. (:pull:`3647`)

#. `@stephenworsley`_ improved the handling of
   :class:`iris.coords.CellMeasure`\ s in the :class:`~iris.cube.Cube`
   statistical operations :meth:`~iris.cube.Cube.collapsed`,
   :meth:`~iris.cube.Cube.aggregated_by` and
   :meth:`~iris.cube.Cube.rolling_window`. These previously removed every
   :class:`~iris.coords.CellMeasure` attached to the cube.  Now, a
   :class:`~iris.coords.CellMeasure` will only be removed if it is associated
   with an axis over which the statistic is being run. (:pull:`3549`)

#. `@stephenworsley`_, `@pp-mo`_ and `@abooton`_ added support for
   `CF Ancillary Data`_ variables.  These are created as
   :class:`iris.coords.AncillaryVariable`, and appear as components of cubes
   much like :class:`~iris.coords.AuxCoord`\ s, with the new
   :class:`~iris.cube.Cube` methods
   :meth:`~iris.cube.Cube.add_ancillary_variable`,
   :meth:`~iris.cube.Cube.remove_ancillary_variable`,
   :meth:`~iris.cube.Cube.ancillary_variable`,
   :meth:`~iris.cube.Cube.ancillary_variables` and
   :meth:`~iris.cube.Cube.ancillary_variable_dims`.
   They are loaded from and saved to NetCDF-CF files.  Special support for
   `Quality Flags`_ is also provided, to ensure they load and save with
   appropriate units. (:pull:`3800`)

#. `@bouweandela`_ implemented lazy regridding for the
   :class:`~iris.analysis.Linear`, :class:`~iris.analysis.Nearest`, and
   :class:`~iris.analysis.AreaWeighted` regridding schemes. (:pull:`3701`)

#. `@bjlittle`_ added `logging`_ support within :mod:`iris.analysis.maths`,
   :mod:`iris.common.metadata`, and :mod:`iris.common.resolve`. Each module
   defines a :class:`logging.Logger` instance called ``logger`` with a default
   ``level`` of ``INFO``. To enable ``DEBUG`` logging use
   ``logger.setLevel("DEBUG")``. (:pull:`3785`)

#. `@bjlittle`_ added the :mod:`iris.common.resolve` module, which provides
   infrastructure to support the analysis, identification and combination
   of metadata common between two :class:`~iris.cube.Cube` operands into a
   single resultant :class:`~iris.cube.Cube` that will be auto-transposed,
   and with the appropriate broadcast shape. (:pull:`3785`)

#. `@bjlittle`_ added the :ref:`common metadata API <metadata>`, which provides
   a unified treatment of metadata across Iris, and allows users to easily
   manage and manipulate their metadata in a consistent way. (:pull:`3785`)

#. `@bjlittle`_ added :ref:`lenient metadata <lenient metadata>` support, to
   allow users to control **strict** or **lenient** metadata equivalence,
   difference and combination. (:pull:`3785`)

#. `@bjlittle`_ added :ref:`lenient cube maths <lenient maths>` support and
   resolved several long standing major issues with cube arithmetic regarding
   a more robust treatment of cube broadcasting, cube dimension auto-transposition,
   and preservation of common metadata and coordinates during cube math operations.
   Resolves :issue:`1887`, :issue:`2765`, and :issue:`3478`. (:pull:`3785`)

#. `@pp-mo`_ and `@TomekTrzeciak`_ enhanced :meth:`~iris.cube.Cube.collapse` to allow a 1-D weights array when
   collapsing over a single dimension.
   Previously, the weights had to be the same shape as the whole cube, which could cost a lot of memory in some cases.
   The 1-D form is supported by most weighted array statistics (such as :meth:`np.average`), so this now works
   with the corresponding Iris schemes (in that case, :const:`~iris.analysis.MEAN`).  (:pull:`3943`)


🐛 Bugs Fixed
=============

#. `@stephenworsley`_ fixed :meth:`~iris.cube.Cube.remove_coord` to now also
   remove derived coordinates by removing aux_factories. (:pull:`3641`)

#. `@jonseddon`_ fixed ``isinstance(cube, collections.Iterable)`` to now behave
   as expected if a :class:`~iris.cube.Cube` is iterated over, while also
   ensuring that ``TypeError`` is still raised. (Fixed by setting the
   ``__iter__()`` method in :class:`~iris.cube.Cube` to ``None``).
   (:pull:`3656`)

#. `@stephenworsley`_ enabled cube concatenation along an axis shared by cell
   measures; these cell measures are now concatenated together in the resulting
   cube. Such a scenario would previously cause concatenation to inappropriately
   fail. (:pull:`3566`)

#. `@stephenworsley`_ newly included :class:`~iris.coords.CellMeasure`\ s in
   :class:`~iris.cube.Cube` copy operations. Previously copying a
   :class:`~iris.cube.Cube` would ignore any attached
   :class:`~iris.coords.CellMeasure`. (:pull:`3546`)

#. `@bjlittle`_ set a :class:`~iris.coords.CellMeasure`'s
   ``measure`` attribute to have a default value of ``area``.
   Previously, the ``measure`` was provided as a keyword argument to
   :class:`~iris.coords.CellMeasure` with a default value of ``None``, which
   caused a ``TypeError`` when no ``measure`` was provided, since ``area`` or
   ``volume`` are the only accepted values. (:pull:`3533`)

#. `@trexfeathers`_ set **all** plot types in :mod:`iris.plot` to now use
   :obj:`matplotlib.dates.date2num` to format date/time coordinates for use on a plot
   axis (previously :meth:`~iris.plot.pcolor` and :meth:`~iris.plot.pcolormesh`
   did not include this behaviour). (:pull:`3762`)

#. `@trexfeathers`_ changed date/time axis labels in :mod:`iris.quickplot` to
   now **always** be based on the ``epoch`` used in :obj:`matplotlib.dates.date2num`
   (previously would take the unit from a time coordinate, if present, even
   though the coordinate's value had been changed via ``date2num``).
   (:pull:`3762`)

#. `@pp-mo`_ newly included attributes of cell measures in NETCDF-CF
   file loading; they were previously being discarded. They are now available on
   the :class:`~iris.coords.CellMeasure` in the loaded :class:`~iris.cube.Cube`.
   (:pull:`3800`)

#. `@pp-mo`_ fixed the netcdf loader to now handle any grid-mapping
   variables with missing ``false_easting`` and ``false_northing`` properties,
   which was previously failing for some coordinate systems. See :issue:`3629`.
   (:pull:`3804`)

#. `@stephenworsley`_ changed the way tick labels are assigned from string coords.
   Previously, the first tick label would occasionally be duplicated. This also
   removes the use of the deprecated `matplotlib`_ ``IndexFormatter``. (:pull:`3857`)

#. `@znicholls`_ fixed :meth:`~iris.quickplot._title` to only check
   ``units.is_time_reference`` if the ``units`` symbol is not used. (:pull:`3902`)

#. `@rcomer`_ fixed a bug whereby numpy array type attributes on a cube's
   coordinates could prevent printing it.  See :issue:`3921`.  (:pull:`3922`)

.. _whatsnew 3.0.2 changes:

💣 Incompatible Changes
=======================

#. `@pp-mo`_ rationalised :class:`~iris.cube.CubeList` extraction
   methods:

   The former method ``iris.cube.CubeList.extract_strict``, and the ``strict``
   keyword of the :meth:`~iris.cube.CubeList.extract` method have been removed,
   and are replaced by the new routines :meth:`~iris.cube.CubeList.extract_cube`
   and :meth:`~iris.cube.CubeList.extract_cubes`.
   The new routines perform the same operation, but in a style more like other
   ``Iris`` functions such as :meth:`~iris.load_cube` and :meth:`~iris.load_cubes`.
   Unlike ``strict`` extraction, the type of return value is now completely
   consistent : :meth:`~iris.cube.CubeList.extract_cube` always returns a
   :class:`~iris.cube.Cube`, and :meth:`~iris.cube.CubeList.extract_cubes`
   always returns an :class:`iris.cube.CubeList` of a length equal to the
   number of constraints. (:pull:`3715`)

#. `@pp-mo`_ removed the former function
   ``iris.analysis.coord_comparison``. (:pull:`3562`)

#. `@bjlittle`_ moved the
   :func:`iris.experimental.equalise_cubes.equalise_attributes` function from
   the :mod:`iris.experimental` module into the :mod:`iris.util` module.  Please
   use the :func:`iris.util.equalise_attributes` function instead.
   (:pull:`3527`)

#. `@bjlittle`_ removed the module ``iris.experimental.concatenate``. In
   ``v1.6.0`` the experimental ``concatenate`` functionality was moved to the
   :meth:`iris.cube.CubeList.concatenate` method.  Since then, calling the
   :func:`iris.experimental.concatenate.concatenate` function raised an
   exception. (:pull:`3523`)

#. `@stephenworsley`_ changed the default units of :class:`~iris.coords.DimCoord`
   and :class:`~iris.coords.AuxCoord` from `"1"` to `"unknown"`.  (:pull:`3795`)

#. `@stephenworsley`_ changed Iris objects loaded from NetCDF-CF files to have
   ``units='unknown'`` where the corresponding NetCDF variable has no ``units``
   property. Previously these cases defaulted to ``units='1'``.
   This affects loading of coordinates whose file variable has no "units"
   attribute (not valid, under `CF units rules`_):  These will now have units
   of `"unknown"`, rather than `"1"`, which **may prevent the creation of
   a hybrid vertical coordinate**.  While these cases used to "work", this was
   never really correct behaviour. (:pull:`3795`)

#. `@SimonPeatman`_ added attribute ``var_name`` to coordinates created by the
   :func:`iris.analysis.trajectory.interpolate` function.  This prevents
   duplicate coordinate errors in certain circumstances. (:pull:`3718`)

#. `@bjlittle`_ aligned the :func:`iris.analysis.maths.apply_ufunc` with the
   rest of the :mod:`iris.analysis.maths` API by changing its keyword argument
   from ``other_cube`` to ``other``. (:pull:`3785`)

#. `@bjlittle`_ changed the :meth:`iris.analysis.maths.IFunc.__call__` to ignore
   any surplus ``other`` keyword argument for a ``data_func`` that requires
   **only one** argument. This aligns the behaviour of
   :meth:`iris.analysis.maths.IFunc.__call__` with
   :func:`~iris.analysis.maths.apply_ufunc`. Previously a ``ValueError``
   exception was raised. (:pull:`3785`)


.. _whatsnew 3.0.2 deprecations:

🔥 Deprecations
===============

#. `@stephenworsley`_ removed the deprecated :class:`iris.Future` flags
   ``cell_date_time_objects``, ``netcdf_promote``, ``netcdf_no_unlimited`` and
   ``clip_latitudes``. (:pull:`3459`)

#. `@stephenworsley`_ changed :attr:`iris.fileformats.pp.PPField.lbproc` to be an
   ``int``. The deprecated attributes ``flag1``, ``flag2`` etc. have been
   removed from it. (:pull:`3461`)

#. `@bjlittle`_ deprecated :func:`~iris.util.as_compatible_shape` in preference
   for :class:`~iris.common.resolve.Resolve` e.g., ``Resolve(src, tgt)(tgt.core_data())``.
   The :func:`~iris.util.as_compatible_shape` function will be removed in a future
   release of Iris. (:pull:`3892`)


🔗 Dependencies
===============

#. `@stephenworsley`_, `@trexfeathers`_ and `@bjlittle`_ removed ``Python2``
   support, modernising the codebase by switching to exclusive ``Python3``
   support. (:pull:`3513`)

#. `@bjlittle`_ improved the developer set up process. Configuring Iris and
   :ref:`installing_from_source` as a developer with all the required package
   dependencies is now easier with our curated conda environment YAML files.
   (:pull:`3812`)

#. `@stephenworsley`_ pinned Iris to require `Dask`_ ``>=2.0``. (:pull:`3460`)

#. `@stephenworsley`_ and `@trexfeathers`_ pinned Iris to require
   `Cartopy`_ ``>=0.18``, in order to remain compatible with the latest version
   of `matplotlib`_. (:pull:`3762`)

#. `@bjlittle`_ unpinned Iris to use the latest version of `matplotlib`_.
   Supporting ``Iris`` for both ``Python2`` and ``Python3`` had resulted in
   pinning our dependency on `matplotlib`_ at ``v2.x``.  But this is no longer
   necessary now that ``Python2`` support has been dropped. (:pull:`3468`)

#. `@stephenworsley`_ and `@trexfeathers`_ unpinned Iris to use the latest version
   of `Proj`_. (:pull:`3762`)

#. `@stephenworsley`_ and `@trexfeathers`_ removed GDAL from the extensions
   dependency group. We no longer consider it to be an extension. (:pull:`3762`)


.. _whatsnew 3.0.2 docs:

📚 Documentation
================

#. `@tkknight`_ moved the
   :ref:`sphx_glr_generated_gallery_oceanography_plot_orca_projection.py`
   from the general part of the gallery to oceanography. (:pull:`3761`)

#. `@tkknight`_ updated documentation to use a modern sphinx theme and be
   served from https://scitools-iris.readthedocs.io/en/latest/. (:pull:`3752`)

#. `@bjlittle`_ added support for the `black`_ code formatter. This is
   now automatically checked on GitHub PRs, replacing the older, unittest-based
   ``iris.tests.test_coding_standards.TestCodeFormat``. Black provides automatic
   code format correction for most IDEs.  See the new developer guide section on
   :ref:`code_formatting`. (:pull:`3518`)

#. `@tkknight`_ and `@trexfeathers`_ refreshed the :ref:`whats_new_contributions`
   for the :ref:`iris_whatsnew`. This includes always creating the ``latest``
   what's new page so it appears on the latest documentation at
   https://scitools-iris.readthedocs.io/en/latest/whatsnew. This resolves
   :issue:`2104`, :issue:`3451`, :issue:`3818`, :issue:`3837`.  Also updated the
   :ref:`iris_development_releases_steps` to follow when making a release.
   (:pull:`3769`, :pull:`3838`, :pull:`3843`)

#. `@tkknight`_ enabled the PDF creation of the documentation on the
   `Read the Docs`_ service. The PDF may be accessed by clicking on the version
   at the bottom of the side bar, then selecting ``PDF`` from the ``Downloads``
   section. (:pull:`3765`)

#. `@stephenworsley`_ added a warning to the
   :func:`iris.analysis.cartography.project` function regarding its behaviour on
   projections with non-rectangular boundaries. (:pull:`3762`)

#. `@stephenworsley`_ added the :ref:`cube_maths_combining_units` section to the
   user guide to clarify how ``Units`` are handled during cube arithmetic.
   (:pull:`3803`)

#. `@tkknight`_ overhauled the :ref:`developers_guide` including information on
   getting involved in becoming a contributor and general structure of the
   guide.  This resolves :issue:`2170`, :issue:`2331`, :issue:`3453`,
   :issue:`314`, :issue:`2902`. (:pull:`3852`)

#. `@rcomer`_ added argument descriptions to the :class:`~iris.coords.DimCoord`
   docstring. (:pull:`3681`)

#. `@tkknight`_ added two url's to be ignored for the ``make linkcheck``.  This
   will ensure the Iris github project is not repeatedly hit during the
   linkcheck for issues and pull requests as it can result in connection
   refused and thus travis-ci_ job failures.  For more information on linkcheck,
   see :ref:`contributing.documentation.testing`.  (:pull:`3873`)

#. `@tkknight`_ enabled the napolean_ package that is used by sphinx_ to cater
   for the existing google style docstrings and to also allow for `numpy`_
   docstrings.  This resolves :issue:`3841`. (:pull:`3871`)

#. `@tkknight`_ configured ``sphinx-build`` to promote warnings to errors when
   building the documentation via ``make html``.  This will minimise technical
   debt accruing for the documentation.  (:pull:`3877`)

#. `@tkknight`_ updated :ref:`installing_iris` to include a reference to
   Windows Subsystem for Linux. (:pull:`3885`)

#. `@tkknight`_ updated the :ref:`iris_docs` homepage to include panels so the
   links are more visible to users.  This uses the sphinx-panels_ extension.
   (:pull:`3884`)

#. `@bjlittle`_ created the :ref:`Further topics <further topics>` section and
   included documentation for :ref:`metadata`, :ref:`lenient metadata`, and
   :ref:`lenient maths`. (:pull:`3890`)

#. `@jonseddon`_ updated the CF version of the netCDF saver in the
   :ref:`saving_iris_cubes` section and in the equivalent function docstring.
   (:pull:`3925`)

#. `@bjlittle`_ applied `Title Case Capitalization`_ to the documentation.
   (:pull:`3940`)


💼 Internal
===========

#. `@pp-mo`_ and `@lbdreyer`_ removed all Iris test dependencies on `iris-grib`_
   by transferring all relevant content to the `iris-grib`_ repository. (:pull:`3662`,
   :pull:`3663`, :pull:`3664`, :pull:`3665`, :pull:`3666`, :pull:`3669`,
   :pull:`3670`, :pull:`3671`, :pull:`3672`, :pull:`3742`, :pull:`3746`)

#. `@lbdreyer`_ and `@pp-mo`_ overhauled the handling of dimensional
   metadata to remove duplication. (:pull:`3422`, :pull:`3551`)

#. `@trexfeathers`_ simplified the standard license header for all files, which
   removes the need to repeatedly update year numbers in the header.
   (:pull:`3489`)

#. `@stephenworsley`_ changed the numerical values in tests involving the
   Robinson projection due to improvements made in
   `Proj`_. (:pull:`3762`) (see also `Proj#1292`_ and `Proj#2151`_)

#. `@stephenworsley`_ changed tests to account for more detailed descriptions of
   projections in `GDAL`_. (:pull:`3762`) (see also `GDAL#1185`_)

#. `@stephenworsley`_ changed tests to account for `GDAL`_ now saving fill values
   for data without masked points. (:pull:`3762`)

#. `@trexfeathers`_ changed every graphics test that includes `Cartopy's coastlines`_
   to account for new adaptive coastline scaling. (:pull:`3762`)
   (see also `Cartopy#1105`_)

#. `@trexfeathers`_ changed graphics tests to account for some new default
   grid-line spacing in `Cartopy`_. (:pull:`3762`) (see also `Cartopy#1117`_)

#. `@trexfeathers`_ added additional acceptable graphics test targets to account
   for very minor changes in `matplotlib`_ version ``3.3`` (colormaps, fonts and
   axes borders). (:pull:`3762`)

#. `@rcomer`_ corrected the `matplotlib`_ backend in Iris tests to ignore
   :obj:`matplotlib.rcdefaults`, instead the tests will **always** use ``agg``.
   (:pull:`3846`)

#. `@bjlittle`_ migrated the `black`_ support from ``19.10b0`` to ``20.8b1``.
   (:pull:`3866`)

#. `@lbdreyer`_ updated the CF standard name table to the latest version: `v75`_.
   (:pull:`3867`)

#. `@bjlittle`_ added :pep:`517` and :pep:`518` support for building and
   installing Iris, in particular to handle the `PyKE`_ package dependency.
   (:pull:`3812`)

#. `@bjlittle`_ added metadata support for comparing :attr:`~iris.cube.Cube.attributes`
   dictionaries that contain `numpy`_ arrays using `xxHash`_, an extremely fast
   non-cryptographic hash algorithm, running at RAM speed limits.

#. `@bjlittle`_ added the ``iris.tests.assertDictEqual`` method to override
   :meth:`unittest.TestCase.assertDictEqual` in order to cope with testing
   metadata :attr:`~iris.cube.Cube.attributes` dictionary comparison where
   the value of a key may be a `numpy`_ array. (:pull:`3785`)

#. `@bjlittle`_ added the :func:`~iris.config.get_logger` function for creating
   a generic :class:`logging.Logger` with a :class:`logging.StreamHandler` and
   custom :class:`logging.Formatter`. (:pull:`3785`)

#. `@owena11`_ identified and optimised a bottleneck in ``FieldsFile`` header
   loading due to the use of :func:`numpy.fromfile`. (:pull:`3791`)

#. `@znicholls`_ added a test for plotting with the label being taken from the unit's symbol,
   see :meth:`~iris.tests.test_quickplot.TestLabels.test_pcolormesh_str_symbol` (:pull:`3902`).

#. `@znicholls`_ made :func:`~iris.tests.idiff.step_over_diffs` robust to hyphens (``-``) in
   the input path (i.e. the ``result_dir`` argument) (:pull:`3902`).

#. `@bjlittle`_ migrated the CIaaS from `travis-ci`_ to `cirrus-ci`_, and removed `stickler-ci`_
   support. (:pull:`3928`)

#. `@bjlittle`_ introduced `nox`_ as a common and easy entry-point for test automation.
   It can be used both from `cirrus-ci`_ in the cloud, and locally by the developer to
   run the Iris tests, the doc-tests, the gallery doc-tests, and lint Iris
   with `flake8`_ and `black`_. (:pull:`3928`)

.. _Read the Docs: https://scitools-iris.readthedocs.io/en/latest/
.. _CF units rules: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#units
.. _CF Ancillary Data: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#ancillary-data
.. _Quality Flags: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#flags
.. _iris-grib: https://github.com/SciTools/iris-grib
.. _Cartopy: https://github.com/SciTools/cartopy
.. _Cartopy's coastlines: https://scitools.org.uk/cartopy/docs/latest/matplotlib/geoaxes.html?highlight=coastlines#cartopy.mpl.geoaxes.GeoAxes.coastlines
.. _Cartopy#1105: https://github.com/SciTools/cartopy/pull/1105
.. _Cartopy#1117: https://github.com/SciTools/cartopy/pull/1117
.. _Dask: https://github.com/dask/dask
.. _Proj: https://github.com/OSGeo/PROJ
.. _black: https://black.readthedocs.io/en/stable/
.. _Proj#1292: https://github.com/OSGeo/PROJ/pull/1292
.. _Proj#2151: https://github.com/OSGeo/PROJ/pull/2151
.. _GDAL: https://github.com/OSGeo/gdal
.. _GDAL#1185: https://github.com/OSGeo/gdal/pull/1185
.. _@MoseleyS: https://github.com/MoseleyS
.. _@stephenworsley: https://github.com/stephenworsley
.. _@pp-mo: https://github.com/pp-mo
.. _@abooton: https://github.com/abooton
.. _@bouweandela: https://github.com/bouweandela
.. _@bjlittle: https://github.com/bjlittle
.. _@trexfeathers: https://github.com/trexfeathers
.. _@jonseddon: https://github.com/jonseddon
.. _@tkknight: https://github.com/tkknight
.. _@lbdreyer: https://github.com/lbdreyer
.. _@SimonPeatman: https://github.com/SimonPeatman
.. _@TomekTrzeciak: https://github.com/TomekTrzeciak
.. _@rcomer: https://github.com/rcomer
.. _@jvegasbsc: https://github.com/jvegasbsc
.. _@zklaus: https://github.com/zklaus
.. _@znicholls: https://github.com/znicholls
.. _ESMValTool: https://github.com/ESMValGroup/ESMValTool
.. _v75: https://cfconventions.org/Data/cf-standard-names/75/build/cf-standard-name-table.html
.. _sphinx-panels: https://sphinx-panels.readthedocs.io/en/latest/
.. _logging: https://docs.python.org/3/library/logging.html
.. _numpy: https://github.com/numpy/numpy
.. _xxHash: https://github.com/Cyan4973/xxHash
.. _PyKE: https://pypi.org/project/scitools-pyke/
.. _@owena11: https://github.com/owena11
.. _GitHub: https://github.com/SciTools/iris/issues/new/choose
.. _readthedocs: https://readthedocs.org/
.. _CF Conventions and Metadata: https://cfconventions.org/
.. _flake8: https://flake8.pycqa.org/en/stable/
.. _nox: https://nox.thea.codes/en/stable/
.. _Title Case Capitalization: https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case
.. _travis-ci: https://travis-ci.org/github/SciTools/iris
.. _stickler-ci: https://stickler-ci.com/
.. _@jamesp: https://github.com/jamesp
