Skip to content

Commit

Permalink
Support for py38 and Cartopy 0.19 (#4130)
Browse files Browse the repository at this point in the history
* mpl 3.4.1 updates (#4087)

* replace most recent hashes (#4112)

* Corrected plot_anomaly_log_colouring for new Matplotlib linscale rules. (#4115)

* Cartopy 0.19 updates (#4128)

* Use assertArrayAllClose for sqrt test (#4118)

* using AllClose for sqrt test

* Omitting the checksum from test cml

* use ArrayAllClose (rebase reset it?)

* Iris py38 (#3976)

* support for py38

* update CI and noxfile

* enforce alphabetical xml element attribute order

* full tests for py38 + fix docs-tests

* add whatsnew entry

* update doc-strings + review actions

* Alternate xml handling routine (#29)

* all xml tests pass for nox tests-3.8

* restored docstrings

* move sort_xml_attrs

* make sort_xml_attrs a classmethod

* update sort_xml_attr doc-string

Co-authored-by: Bill Little <[email protected]>

* add jamesp to whatsnew + minor tweak

Co-authored-by: James Penn <[email protected]>

* reinstate black and nox links

* Linkcheck update (#4104)

* Updated links

* Added login remark

* Removed extra space

* change to kick cirrus

* kick cirrus

* test verbose on cirrus

* Removed test settings.

Co-authored-by: Bill Little <[email protected]>
Co-authored-by: Martin Yeo <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: James Penn <[email protected]>
Co-authored-by: James Penn <[email protected]>
Co-authored-by: tkknight <[email protected]>
  • Loading branch information
7 people authored May 13, 2021
1 parent 4885e12 commit e537afa
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 65 deletions.
12 changes: 7 additions & 5 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ test_minimal_task:
PY_VER: 3.6
env:
PY_VER: 3.7
env:
PY_VER: 3.8
name: "${CIRRUS_OS}: py${PY_VER} tests (minimal)"
container:
image: gcc:latest
Expand All @@ -143,6 +145,8 @@ test_full_task:
PY_VER: 3.6
env:
PY_VER: 3.7
env:
PY_VER: 3.8
name: "${CIRRUS_OS}: py${PY_VER} tests (full)"
container:
image: gcc:latest
Expand Down Expand Up @@ -172,9 +176,7 @@ gallery_task:
<< : *CREDITS_TEMPLATE
matrix:
env:
PY_VER: 3.6
env:
PY_VER: 3.7
PY_VER: 3.8
name: "${CIRRUS_OS}: py${PY_VER} doc tests (gallery)"
container:
image: gcc:latest
Expand Down Expand Up @@ -204,7 +206,7 @@ doctest_task:
<< : *CREDITS_TEMPLATE
matrix:
env:
PY_VER: 3.7
PY_VER: 3.8
name: "${CIRRUS_OS}: py${PY_VER} doc tests"
container:
image: gcc:latest
Expand Down Expand Up @@ -240,7 +242,7 @@ linkcheck_task:
<< : *CREDITS_TEMPLATE
matrix:
env:
PY_VER: 3.7
PY_VER: 3.8
name: "${CIRRUS_OS}: py${PY_VER} doc link check"
container:
image: gcc:latest
Expand Down
6 changes: 3 additions & 3 deletions docs/iris/gallery_code/general/plot_anomaly_log_colouring.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ def main():
# Create a 'logarithmic' data normalization.
anom_norm = mcols.SymLogNorm(
linthresh=minimum_log_level,
linscale=0,
linscale=0.01,
vmin=-maximum_scale_level,
vmax=maximum_scale_level,
)
# Setting "linthresh=minimum_log_level" makes its non-logarithmic
# data range equal to our 'zero band'.
# Setting "linscale=0" maps the whole zero band to the middle colour value
# (i.e. 0.5), which is the neutral point of a "diverging" style colormap.
# Setting "linscale=0.01" maps the whole zero band to the middle colour value
# (i.e., 0.5), which is the neutral point of a "diverging" style colormap.

# Create an Axes, specifying the map projection.
plt.axes(projection=ccrs.LambertConformal())
Expand Down
2 changes: 1 addition & 1 deletion docs/iris/src/developers_guide/gitwash/git_links.inc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
.. _git config: http://schacon.github.com/git/git-config.html

.. _linux git workflow: http://www.mail-archive.com/[email protected]/msg39091.html
.. _deleting master on github: http://matthew-brett.github.com/pydagogue/gh_delete_master.html
.. _deleting master on github: https://matthew-brett.github.io/pydagogue/gh_delete_master.html

.. |emdash| unicode:: U+02014
7 changes: 3 additions & 4 deletions docs/iris/src/further_topics/metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,12 @@ create a **new** instance directly from the metadata class itself,
>>> DimCoordMetadata._make(values)
DimCoordMetadata(standard_name=1, long_name=2, var_name=3, units=4, attributes=5, coord_system=6, climatological=7, circular=8)

It is also possible to easily convert ``metadata`` to an `OrderedDict`_
It is also possible to easily convert ``metadata`` to an `dict`_
using the `namedtuple._asdict`_ method. This can be particularly handy when a
standard Python built-in container is required to represent your ``metadata``,

>>> metadata._asdict()
OrderedDict([('standard_name', 'longitude'), ('long_name', None), ('var_name', 'longitude'), ('units', Unit('degrees')), ('attributes', {'grinning face': '🙃'}), ('coord_system', GeogCS(6371229.0)), ('climatological', False), ('circular', False)])
{'standard_name': 'longitude', 'long_name': None, 'var_name': 'longitude', 'units': Unit('degrees'), 'attributes': {'grinning face': '🙃'}, 'coord_system': GeogCS(6371229.0), 'climatological': False, 'circular': False}

Using the `namedtuple._replace`_ method allows you to create a new metadata
class instance, but replacing specified members with **new** associated values,
Expand Down Expand Up @@ -943,7 +943,7 @@ such as a `dict`_,

>>> mapping = latitude.metadata._asdict()
>>> mapping
OrderedDict([('standard_name', 'latitude'), ('long_name', None), ('var_name', 'latitude'), ('units', Unit('degrees')), ('attributes', {}), ('coord_system', GeogCS(6371229.0)), ('climatological', False), ('circular', False)])
{'standard_name': 'latitude', 'long_name': None, 'var_name': 'latitude', 'units': Unit('degrees'), 'attributes': {}, 'coord_system': GeogCS(6371229.0), 'climatological': False, 'circular': False}
>>> longitude.metadata = mapping
>>> longitude.metadata
DimCoordMetadata(standard_name='latitude', long_name=None, var_name='latitude', units=Unit('degrees'), attributes={}, coord_system=GeogCS(6371229.0), climatological=False, circular=False)
Expand Down Expand Up @@ -1000,7 +1000,6 @@ values. All other metadata members will be left unaltered.
.. _NetCDF: https://www.unidata.ucar.edu/software/netcdf/
.. _NetCDF CF Metadata Conventions: https://cfconventions.org/
.. _NumPy: https://github.com/numpy/numpy
.. _OrderedDict: https://docs.python.org/3/library/collections.html#collections.OrderedDict
.. _Parametric Vertical Coordinate: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#parametric-vertical-coordinate
.. _rich comparison: https://www.python.org/dev/peps/pep-0207/
.. _SciTools/iris: https://github.com/SciTools/iris
Expand Down
4 changes: 2 additions & 2 deletions docs/iris/src/installing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ any WSL_ distributions.

.. _WSL: https://docs.microsoft.com/en-us/windows/wsl/install-win10

.. note:: Iris currently supports and is tested against **Python 3.6** and
**Python 3.7**.
.. note:: Iris is currently supported and tested against Python ``3.6``,
``3.7``, and ``3.8``.


.. _installing_using_conda:
Expand Down
77 changes: 70 additions & 7 deletions lib/iris/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,21 @@ def shape(self):
return self._values_dm.shape

def xml_element(self, doc):
"""Return a DOM element describing this metadata."""
"""
Create the :class:`xml.dom.minidom.Element` that describes this
:class:`_DimensionalMetadata`.
Args:
* doc:
The parent :class:`xml.dom.minidom.Document`.
Returns:
The :class:`xml.dom.minidom.Element` that will describe this
:class:`_DimensionalMetadata`, and the dictionary of attributes
that require to be added to this element.
"""
# Create the XML element as the camelCaseEquivalent of the
# class name.
element_name = type(self).__name__
Expand Down Expand Up @@ -882,6 +896,20 @@ def cube_dims(self, cube):
return cube.cell_measure_dims(self)

def xml_element(self, doc):
"""
Create the :class:`xml.dom.minidom.Element` that describes this
:class:`CellMeasure`.
Args:
* doc:
The parent :class:`xml.dom.minidom.Document`.
Returns:
The :class:`xml.dom.minidom.Element` that describes this
:class:`CellMeasure`.
"""
# Create the XML element as the camelCaseEquivalent of the
# class name
element = super().xml_element(doc=doc)
Expand Down Expand Up @@ -2229,14 +2257,26 @@ def nearest_neighbour_index(self, point):
return result_index

def xml_element(self, doc):
"""Return a DOM element describing this Coord."""
"""
Create the :class:`xml.dom.minidom.Element` that describes this
:class:`Coord`.
Args:
* doc:
The parent :class:`xml.dom.minidom.Document`.
Returns:
The :class:`xml.dom.minidom.Element` that will describe this
:class:`DimCoord`, and the dictionary of attributes that require
to be added to this element.
"""
# Create the XML element as the camelCaseEquivalent of the
# class name
element = super().xml_element(doc=doc)

element.setAttribute("points", self._xml_array_repr(self.points))

# Add bounds handling
# Add bounds, points are handled by the parent class.
if self.has_bounds():
element.setAttribute("bounds", self._xml_array_repr(self.bounds))

Expand Down Expand Up @@ -2613,7 +2653,20 @@ def is_monotonic(self):
return True

def xml_element(self, doc):
"""Return DOM element describing this :class:`iris.coords.DimCoord`."""
"""
Create the :class:`xml.dom.minidom.Element` that describes this
:class:`DimCoord`.
Args:
* doc:
The parent :class:`xml.dom.minidom.Document`.
Returns:
The :class:`xml.dom.minidom.Element` that describes this
:class:`DimCoord`.
"""
element = super().xml_element(doc)
if self.circular:
element.setAttribute("circular", str(self.circular))
Expand Down Expand Up @@ -2754,7 +2807,17 @@ def __add__(self, other):

def xml_element(self, doc):
"""
Return a dom element describing itself
Create the :class:`xml.dom.minidom.Element` that describes this
:class:`CellMethod`.
Args:
* doc:
The parent :class:`xml.dom.minidom.Document`.
Returns:
The :class:`xml.dom.minidom.Element` that describes this
:class:`CellMethod`.
"""
cellMethod_xml_element = doc.createElement("cellMethod")
Expand Down
56 changes: 56 additions & 0 deletions lib/iris/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def __getslice__(self, start, stop):

def xml(self, checksum=False, order=True, byteorder=True):
"""Return a string of the XML that this list of cubes represents."""

doc = Document()
cubes_xml_element = doc.createElement("cubes")
cubes_xml_element.setAttribute("xmlns", XML_NAMESPACE_URI)
Expand All @@ -239,6 +240,7 @@ def xml(self, checksum=False, order=True, byteorder=True):
doc.appendChild(cubes_xml_element)

# return our newly created XML string
doc = Cube._sort_xml_attrs(doc)
return doc.toprettyxml(indent=" ")

def extract(self, constraints):
Expand Down Expand Up @@ -755,6 +757,59 @@ class Cube(CFVariableMixin):
#: is similar to Fortran or Matlab, but different than numpy.
__orthogonal_indexing__ = True

@classmethod
def _sort_xml_attrs(cls, doc):
"""
Takes an xml document and returns a copy with all element
attributes sorted in alphabetical order.
This is a private utility method required by iris to maintain
legacy xml behaviour beyond python 3.7.
Args:
* doc:
The :class:`xml.dom.minidom.Document`.
Returns:
The :class:`xml.dom.minidom.Document` with sorted element
attributes.
"""
from xml.dom.minidom import Document

def _walk_nodes(node):
"""Note: _walk_nodes is called recursively on child elements."""

# we don't want to copy the children here, so take a shallow copy
new_node = node.cloneNode(deep=False)

# Versions of python <3.8 order attributes in alphabetical order.
# Python >=3.8 order attributes in insert order. For consistent behaviour
# across both, we'll go with alphabetical order always.
# Remove all the attribute nodes, then add back in alphabetical order.
attrs = [
new_node.getAttributeNode(attr_name).cloneNode(deep=True)
for attr_name in sorted(node.attributes.keys())
]
for attr in attrs:
new_node.removeAttributeNode(attr)
for attr in attrs:
new_node.setAttributeNode(attr)

if node.childNodes:
children = [_walk_nodes(x) for x in node.childNodes]
for c in children:
new_node.appendChild(c)

return new_node

nodes = _walk_nodes(doc.documentElement)
new_doc = Document()
new_doc.appendChild(nodes)

return new_doc

def __init__(
self,
data,
Expand Down Expand Up @@ -3399,6 +3454,7 @@ def xml(self, checksum=False, order=True, byteorder=True):
doc.appendChild(cube_xml_element)

# Print our newly created XML
doc = self._sort_xml_attrs(doc)
return doc.toprettyxml(indent=" ")

def _xml_element(self, doc, checksum=False, order=True, byteorder=True):
Expand Down
12 changes: 12 additions & 0 deletions lib/iris/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,18 @@ def contourf(cube, *args, **kwargs):
# any boundary shift.
zorder = result.collections[0].zorder - 0.1
axes = kwargs.get("axes", None)

# Workaround for cartopy#1780. We do not want contour to shrink
# extent.
if axes is None:
_axes = plt.gca()
else:
_axes = axes

# Subsequent calls to dataLim.update_from_data_xy should not ignore
# current extent.
_axes.dataLim.ignore(False)

contour(
cube,
levels=levels,
Expand Down
4 changes: 4 additions & 0 deletions lib/iris/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,10 @@ def assertXMLElement(self, obj, reference_filename):
"""
doc = xml.dom.minidom.Document()
doc.appendChild(obj.xml_element(doc))
# sort the attributes on xml elements before testing against known good state.
# this is to be compatible with stored test output where xml attrs are stored in alphabetical order,
# (which was default behaviour in python <3.8, but changed to insert order in >3.8)
doc = iris.cube.Cube._sort_xml_attrs(doc)
pretty_xml = doc.toprettyxml(indent=" ")
reference_path = self.get_result_path(reference_filename)
self._check_same(
Expand Down
2 changes: 1 addition & 1 deletion lib/iris/tests/results/analysis/sqrt.cml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@
</coord>
</coords>
<cellMethods/>
<data checksum="0x09ef96ca" dtype="float64" shape="(73, 96)"/>
<data dtype="float64" shape="(73, 96)" state="loaded"/>
</cube>
</cubes>
Loading

0 comments on commit e537afa

Please sign in to comment.