From 6a6b2874efef6b7dfb7fafecabc018a058bcc2cf Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 10 Dec 2019 12:53:31 +0000 Subject: [PATCH 01/45] add lenient infra-structure --- lib/iris/analysis/maths.py | 5 + lib/iris/common/_lenient.py | 222 ++++++++++++++++++++++++++++++++++++ lib/iris/common/metadata.py | 15 +++ 3 files changed, 242 insertions(+) create mode 100644 lib/iris/common/_lenient.py diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index 0de97b02f3..6ee5d4adaf 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -19,6 +19,7 @@ from numpy import ma import iris.analysis +from iris.common._lenient import lenient_client import iris.coords import iris.cube import iris.exceptions @@ -216,6 +217,7 @@ def _assert_matching_units(cube, other, operation_name): raise iris.exceptions.NotYetImplementedError(msg) +@lenient_client def add(cube, other, dim=None, in_place=False): """ Calculate the sum of two cubes, or the sum of a cube and a @@ -261,6 +263,7 @@ def add(cube, other, dim=None, in_place=False): ) +@lenient_client def subtract(cube, other, dim=None, in_place=False): """ Calculate the difference between two cubes, or the difference between @@ -380,6 +383,7 @@ def _add_subtract_common( return new_cube +@lenient_client def multiply(cube, other, dim=None, in_place=False): """ Calculate the product of a cube and another cube or coordinate. @@ -475,6 +479,7 @@ def _inplace_common_checks(cube, other, math_op): ) +@lenient_client def divide(cube, other, dim=None, in_place=False): """ Calculate the division of a cube by a cube or coordinate. diff --git a/lib/iris/common/_lenient.py b/lib/iris/common/_lenient.py new file mode 100644 index 0000000000..579eeb6820 --- /dev/null +++ b/lib/iris/common/_lenient.py @@ -0,0 +1,222 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. + +from collections.abc import Iterable +from contextlib import contextmanager +from functools import wraps +from inspect import getmodule +import threading + + +def lenient_client(func): + """ + Decorator that allows a client function/method to declare at runtime that + it is executing and requires lenient behaviour from a prior registered + lenient service function/method. + + Args: + + * func (callable): + Callable function/method to be wrapped by the decorator. + + Returns: + Closure wrapped function/method. + + """ + + @wraps(func) + def lenient_inner(*args, **kwargs): + """ + Closure wrapper function to register the wrapped function/method as + active at runtime before executing it. + + """ + with LENIENT.context(active=qualname(func)): + result = func(*args, **kwargs) + return result + + return lenient_inner + + +def qualname(func): + """Return the fully qualified function/method string name.""" + module = getmodule(func) + return f"{module.__name__}.{func.__qualname__}" + + +def lenient_service(func): + """ + Decorator that allows a function/method to declare that it supports lenient + behaviour. + + Args: + + * func (callable): + Callable function/method to be wrapped by the decorator. + + Returns: + Closure wrapped function/method. + + """ + LENIENT.register(qualname(func)) + + @wraps(func) + def register_inner(*args, **kwargs): + """ + Closure wrapper function to execute the lenient service + function/method. + + """ + return func(*args, **kwargs) + + return register_inner + + +class Lenient(threading.local): + def __init__(self): + """ + A container for managing the run-time lenient options for + pre-defined Iris functions and/or methods. + + To adjust the values simply update the relevant attribute. + For example:: + + iris.LENIENT.example_lenient_flag = False + + Or, equivalently:: + + iris.LENIENT["example_lenient_flag"] = False + + Note that, the values of these options are thread-specific. + + """ + # Currently active lenient service. + self.__dict__["active"] = None + # Define ratified client/service relationships. + client = "iris.analysis.maths.add" + self.__dict__[client] = ("iris.common.metadata.CoordMetadata.__eq__",) + + def __call__(self, func): + result = False + service = qualname(func) + if service in self and self.__dict__[service]: + active = self.__dict__["active"] + if active is not None and active in self: + services = self.__dict__[active] + if isinstance(services, str) or not isinstance( + services, Iterable + ): + services = (services,) + result = service in services + return result + + def __contains__(self, name): + return name in self.__dict__ + + # TODO: Confirm whether this should be part of the API. + def __getattr__(self, name): + if name not in self.__dict__: + cls = self.__class__.__name__ + emsg = f"Invalid {cls!r} option, got {name!r}." + raise AttributeError(emsg) + return self.__dict__[name] + + # TODO: Confirm whether this should be part of the API. + def __getitem__(self, name): + if name not in self.__dict__: + cls = self.__class__.__name__ + emsg = f"Invalid {cls!r} option, got {name!r}." + raise KeyError(emsg) + return self.__dict__[name] + + def __repr__(self): + cls = self.__class__.__name__ + width = len(cls) + 1 + kwargs = [ + "{}={!r}".format(name, self.__dict__[name]) + for name in sorted(self.__dict__.keys()) + ] + joiner = ",\n{}".format(" " * width) + return "{}({})".format(cls, joiner.join(kwargs)) + + def __setattr__(self, name, value): + if name not in self.__dict__: + cls = self.__class__.__name__ + emsg = f"Invalid {cls!r} option, got {name!r}." + raise AttributeError(emsg) + self.__dict__[name] = value + + def __setitem__(self, name, value): + if name not in self.__dict__: + cls = self.__class__.__name__ + emsg = f"Invalid {cls!r} option, got {name!r}." + raise KeyError(emsg) + self.__dict__[name] = value + + @contextmanager + def context(self, **kwargs): + """ + Return a context manager which allows temporary modification of + the lenient option state for the active thread. + + On entry to the context manager, all provided keyword arguments are + applied. On exit from the context manager, the previous lenient option + state is restored. + + For example:: + with iris.LENIENT.context(example_lenient_flag=False): + # ... code that expects some non-lenient behaviour + + .. note:: + + iris.LENIENT.example_future_flag does not exist and is + provided only as an example. + + """ + # Save the current context + current_state = self.__dict__.copy() + # Temporarily update the state. + for name, value in kwargs.items(): + setattr(self, name, value) + try: + yield + finally: + # Restore the original state. + self.__dict__.clear() + self.__dict__.update(current_state) + + def register(self, name): + """ + Register the provided function/method as providing a lenient service. + + Args: + + * name (string): + Fully qualified string name of the function/method. + + """ + self.__dict__[name] = True + + def unregister(self, name): + """ + Unregister the provided function/method as providing a lenient service. + + Args: + + * name (string): + Fully qualified string name of the function/method. + + """ + if name in self.__dict__: + self.__dict__[name] = False + else: + cls = self.__class__.__name__ + emsg = f"Cannot unregister invalid {cls!r} service, got {name!r}." + raise ValueError(emsg) + + +#: Instance that manages all Iris run-time lenient options. +LENIENT = Lenient() diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index e51e34e8f8..5e5bd1d444 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -10,6 +10,8 @@ from functools import wraps import re +from ._lenient import lenient_service + __all__ = [ "AncillaryVariableMetadata", @@ -200,6 +202,19 @@ class CoordMetadata(BaseMetadata): __slots__ = () + @lenient_service + def __eq__(self, other): + result = NotImplemented + if isinstance(other, self.__class__): + result = super().__eq__(other) + return result + + def __ne__(self, other): + result = self.__eq__(other) + if result is not NotImplemented: + result = not result + return result + class CubeMetadata(BaseMetadata): """ From c0b8ffdeeff3cfd167cc152680c44ca6cf779c2c Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 11 Dec 2019 12:02:24 +0000 Subject: [PATCH 02/45] add metadata lenient __eq__ support --- lib/iris/common/_lenient.py | 24 ++++++++-- lib/iris/common/metadata.py | 88 ++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/lib/iris/common/_lenient.py b/lib/iris/common/_lenient.py index 579eeb6820..fa81b99c94 100644 --- a/lib/iris/common/_lenient.py +++ b/lib/iris/common/_lenient.py @@ -93,13 +93,31 @@ def __init__(self): Note that, the values of these options are thread-specific. """ - # Currently active lenient service. + # Currently executing lenient client at runtime. self.__dict__["active"] = None # Define ratified client/service relationships. - client = "iris.analysis.maths.add" - self.__dict__[client] = ("iris.common.metadata.CoordMetadata.__eq__",) + # client = "iris.analysis.maths.add" + # services = ("iris.common.metadata.CoordMetadata.__eq__",) + # self.__dict__[client] = services + # # XXX: testing... + # client = "__main__.myfunc" + # services = ("iris.common.metadata.CoordMetadata.__eq__",) + # self.__dict__[client] = services def __call__(self, func): + """ + Determine whether it is valid for the function/method to provide a + lenient service at runtime to the actively executing lenient client. + + Args: + + * func (callable): + Callable function/method providing the lenient service. + + Returns: + Boolean. + + """ result = False service = qualname(func) if service in self and self.__dict__[service]: diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 5e5bd1d444..c04cc832b4 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -10,7 +10,7 @@ from functools import wraps import re -from ._lenient import lenient_service +from ._lenient import LENIENT, lenient_service __all__ = [ @@ -109,6 +109,7 @@ def token(cls, name): if name is not None: result = _TOKEN_PARSE.match(name) name = result if result is None else name + return name def name(self, default=None, token=False): @@ -153,6 +154,19 @@ def _check(item): return result + @lenient_service + def __eq__(self, other): + result = NotImplemented + if isinstance(other, self.__class__): + if LENIENT(self.__eq__): + # Perform "lenient" comparison. + result = self._compare(other) + else: + # Perform "strict" comparison. + result = super().__eq__(other) + + return result + def __lt__(self, other): # # Support Python2 behaviour for a "<" operation involving a @@ -171,6 +185,39 @@ def _sort_key(item): return _sort_key(self) < _sort_key(other) + def __ne__(self, other): + result = self.__eq__(other) + if result is not NotImplemented: + result = not result + + return result + + def _compare(self, other): + """ + Support lenient metadata equality for coordinates. + + Args: + + * other (CoordMetadata): + The other coordinate metadata participating in the lenient + comparison. + + Returns: + Boolean. + + """ + result = False + # Use the "name" method to leniently compare "standard_name", + # "long_name" and "var_name" in a well defined way. + if self.name() == other.name(): + # Perform "strict" comparison for "units". + result = self.units == other.units + # Perform "lenient" comparison for "attributes". + # This effectively means they are always equal, therefore there is + # no further work to do here. + + return result + class AncillaryVariableMetadata(BaseMetadata): """ @@ -202,17 +249,42 @@ class CoordMetadata(BaseMetadata): __slots__ = () + def _compare(self, other): + """ + Support lenient metadata equality for coordinates. + + Args: + + * other (CoordMetadata): + The other coordinate metadata participating in the lenient + comparison. + + Returns: + Boolean. + + """ + # Perform "strict" comparison for "coord_system" and "climatological". + result = ( + self.coord_system == other.coord_system + and self.climatological == other.climatological + ) + if result: + # Perform lenient comparison of the other members. + result = super()._compare(other) + + return result + @lenient_service def __eq__(self, other): result = NotImplemented if isinstance(other, self.__class__): - result = super().__eq__(other) - return result + if LENIENT(self.__eq__): + # Perform "lenient" comparison. + result = self._compare(other) + else: + # Perform "strict" comparison. + result = super().__eq__(other) - def __ne__(self, other): - result = self.__eq__(other) - if result is not NotImplemented: - result = not result return result @@ -329,6 +401,7 @@ def __eq__(self, other): match = self.cls is other.cls if match: match = self.values == other.values + return match def __getstate__(self): @@ -339,6 +412,7 @@ def __ne__(self, other): match = self.__eq__(other) if match is not NotImplemented: match = not match + return match def __reduce__(self): From 15034ea7b6525b76f152ef6ce7509972feaa3c37 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 13 Dec 2019 14:29:42 +0000 Subject: [PATCH 03/45] complete __eq__, combine and difference support --- lib/iris/common/_lenient.py | 97 ++++-- lib/iris/common/metadata.py | 590 ++++++++++++++++++++++++++++++------ 2 files changed, 569 insertions(+), 118 deletions(-) diff --git a/lib/iris/common/_lenient.py b/lib/iris/common/_lenient.py index fa81b99c94..3cd83fbe41 100644 --- a/lib/iris/common/_lenient.py +++ b/lib/iris/common/_lenient.py @@ -8,9 +8,12 @@ from contextlib import contextmanager from functools import wraps from inspect import getmodule +from itertools import product import threading +# TODO: allow *args to specify the ephemeral services that the client wishes to +# use which are then unpacked in the LENIENT.context def lenient_client(func): """ Decorator that allows a client function/method to declare at runtime that @@ -41,10 +44,36 @@ def lenient_inner(*args, **kwargs): return lenient_inner -def qualname(func): - """Return the fully qualified function/method string name.""" - module = getmodule(func) - return f"{module.__name__}.{func.__qualname__}" +def qualname(func, cls=None): + """ + Return the fully qualified function/method string name. + + Args: + + * func (callable/string): + Callable function/method. The fully qualified name of the callable + function/method is determined, otherwise the string name is used + instead. + + Kwargs: + + * cls (class): + If provided, the class is used to qualify the string name. + + .. note:: + Inherited methods will be qualified with the base class that + defines the method. + + """ + if isinstance(func, str): + result = func + if cls is not None: + result = "{}.{}.{}".format(cls.__module__, cls.__name__, func) + else: + module = getmodule(func) + result = f"{module.__name__}.{func.__qualname__}" + + return result def lenient_service(func): @@ -95,31 +124,57 @@ def __init__(self): """ # Currently executing lenient client at runtime. self.__dict__["active"] = None - # Define ratified client/service relationships. + + # Define lenient services. + # Require to be explicit here for subclass methods that inherit parent + # class behaviour as a service for free. + classes = [ + "AncillaryVariableMetadata", + "CellMeasureMetadata", + "CoordMetadata", + "CubeMetadata", + ] + methods = [ + "__eq__", + "combine", + "difference", + ] + for cls, method in product(classes, methods): + self.__dict__[f"iris.common.metadata.{cls}.{method}"] = True + + # Define lenient client/service relationships. # client = "iris.analysis.maths.add" # services = ("iris.common.metadata.CoordMetadata.__eq__",) # self.__dict__[client] = services - # # XXX: testing... - # client = "__main__.myfunc" - # services = ("iris.common.metadata.CoordMetadata.__eq__",) - # self.__dict__[client] = services - def __call__(self, func): + # XXX: testing... + client = "__main__.myfunc" + services = ("iris.common.metadata.CoordMetadata.__eq__",) + self.__dict__[client] = services + + def __call__(self, func, cls=None): """ Determine whether it is valid for the function/method to provide a lenient service at runtime to the actively executing lenient client. Args: - * func (callable): - Callable function/method providing the lenient service. + * func (callable/string): + Callable function/method providing the lenient service. The fully + qualified name of the callable function/method is determined, + otherwise the string name is used instead. + + Kwargs: + + * cls (class): + If provided, the class is used to qualify the string name. Returns: Boolean. """ result = False - service = qualname(func) + service = qualname(func, cls=cls) if service in self and self.__dict__[service]: active = self.__dict__["active"] if active is not None and active in self: @@ -175,7 +230,7 @@ def __setitem__(self, name, value): self.__dict__[name] = value @contextmanager - def context(self, **kwargs): + def context(self, *args, **kwargs): """ Return a context manager which allows temporary modification of the lenient option state for the active thread. @@ -189,16 +244,22 @@ def context(self, **kwargs): # ... code that expects some non-lenient behaviour .. note:: - - iris.LENIENT.example_future_flag does not exist and is + iris.LENIENT.example_lenient_flag does not exist and is provided only as an example. """ - # Save the current context + # Save the original state. current_state = self.__dict__.copy() - # Temporarily update the state. + # Temporarily update the state with the kwargs first. for name, value in kwargs.items(): setattr(self, name, value) + # Temporarily update the client/services, if provided. + if args: + active = self.__dict__["active"] + if active is None: + active = "context" + self.__dict__["active"] = active + self.__dict__[active] = args try: yield finally: diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index c04cc832b4..688711acb2 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -91,26 +91,279 @@ class BaseMetadata(metaclass=_NamedTupleMeta): __slots__ = () - @classmethod - def token(cls, name): + @lenient_service + def __eq__(self, other): + result = NotImplemented + if hasattr(other, "__class__") and other.__class__ is self.__class__: + if LENIENT("__eq__", cls=self.__class__): + # Perform "lenient" comparison. + result = self._compare_lenient(other) + else: + # Perform "strict" comparison. + result = super().__eq__(other) + + return result + + def __lt__(self, other): + # + # Support Python2 behaviour for a "<" operation involving a + # "NoneType" operand. + # + if not isinstance(other, self.__class__): + return NotImplemented + + def _sort_key(item): + keys = [] + for field in item._fields: + value = getattr(item, field) + keys.extend((value is not None, value)) + return tuple(keys) + + return _sort_key(self) < _sort_key(other) + + def __ne__(self, other): + result = self.__eq__(other) + if result is not NotImplemented: + result = not result + + return result + + def _combine_lenient(self, other): """ - Determine whether the provided name is a valid NetCDF name and thus - safe to represent a single parsable token. + Perform lenient metadata member combination. Args: - * name: - The string name to verify + * other (BaseMetadata): + The other metadata participating in the lenient combination. Returns: - The provided name if valid, otherwise None. + A list of combined metadata member values. """ - if name is not None: - result = _TOKEN_PARSE.match(name) - name = result if result is None else name - return name + def func(field): + left = getattr(self, field) + right = getattr(other, field) + result = None + if field == "units": + # Perform "strict" combination for "units". + result = left if left == right else None + elif self._is_attributes(field, left, right): + result = self._combine_lenient_attributes(left, right) + else: + if left == right: + result = left + elif left is None: + result = right + elif right is None: + result = left + return result + + return [func(field) for field in BaseMetadata._members] + + @staticmethod + def _combine_lenient_attributes(left, right): + """Leniently combine the dictionary members together.""" + sleft = set(left.items()) + sright = set(right.items()) + # Union of common items. + common = sleft & sright + # Items in sleft different from sright. + dsleft = dict(sleft - sright) + # Items in sright different from sleft. + dsright = dict(sright - sleft) + # Union of common item keys with different values. + keys = set(dsleft.keys()) & set(dsright.keys()) + # Remove (in-place) common item keys with different values. + [dsleft.pop(key) for key in keys] + [dsright.pop(key) for key in keys] + # Now bring the result together. + result = dict(common) + result.update(dsleft) + result.update(dsright) + + return result + + def _compare_lenient(self, other): + """ + Support lenient metadata equality for coordinates. + + Args: + + * other (CoordMetadata): + The other coordinate metadata participating in the lenient + comparison. + + Returns: + Boolean. + + """ + result = False + # Use the "name" method to leniently compare "standard_name", + # "long_name" and "var_name" in a well defined way. + if self.name() == other.name(): + # Perform "strict" comparison for "units". + result = self.units == other.units + # Perform "lenient" comparison for "attributes". + # This effectively means they are always equal, therefore there is + # no further work to do here. + + return result + + def _difference_lenient(self, other): + """ + Perform lenient metadata member difference. + + Args: + + * other (BaseMetadata): + The other metadata participating in the lenient difference. + + Returns: + A list of difference metadata member values. + + """ + + def func(field): + left = getattr(self, field) + right = getattr(other, field) + if field == "units": + # Perform "strict" difference for "units". + result = None if left == right else (left, right) + elif self._is_attributes(field, left, right): + result = self._difference_lenient_attributes(left, right) + else: + # Perform "lenient" difference for members. + result = ( + (left, right) + if left is not None and right is not None and left != right + else None + ) + return result + + return [func(field) for field in BaseMetadata._members] + + @staticmethod + def _difference_lenient_attributes(left, right): + """Perform lenient difference between the dictionary members.""" + sleft = set(left.items()) + sright = set(right.items()) + # Items in sleft different from sright. + dsleft = dict(sleft - sright) + # Items in sright different from sleft. + dsright = dict(sright - sleft) + # Union of common item keys with different values. + keys = set(dsleft.keys()) & set(dsright.keys()) + # Keep (in-place) common item keys with different values. + [dsleft.pop(key) for key in list(dsleft.keys()) if key not in keys] + [dsright.pop(key) for key in list(dsright.keys()) if key not in keys] + + return (dsleft, dsright) + + @staticmethod + def _difference_strict_attributes(left, right): + """Perform strict difference between the dictionary members.""" + sleft = set(left.items()) + sright = set(right.items()) + # Items in sleft different from sright. + dsleft = dict(sleft - sright) + # Items in sright different from sleft. + dsright = dict(sright - sleft) + + return (dsleft, dsright) + + @staticmethod + def _is_attributes(field, left, right): + """Determine whether we have two 'attributes' dictionaries.""" + return ( + field == "attributes" + and isinstance(left, Mapping) + and isinstance(right, Mapping) + ) + + @lenient_service + def combine(self, other): + """ + Return a new metadata instance created by combining each of the + associated metadata members. + + Args: + + * other (metadata): + A metadata instance of the same type. + + Returns: + Metadata instance. + + """ + if ( + not hasattr(other, "__class__") + or other.__class__ is not self.__class__ + ): + emsg = "Cannot combine {!r} with {!r}." + raise ValueError(emsg.format(self.__class__.__name__, type(other))) + + if LENIENT("combine", cls=self.__class__): + print("lenient combine") + values = self._combine_lenient(other) + else: + print("strict combine") + + def func(field): + value = getattr(self, field) + return value if value == getattr(other, field) else None + + # Note that, we use "_fields" not "_members". + values = [func(field) for field in self._fields] + + return self.__class__(*values) + + @lenient_service + def difference(self, other): + """ + Return a new metadata instance created by performing a difference + comparison between each of the associated metadata members. + + A returned metadata member with a value of "None" indicates that there + is no difference between the members being compared. Otherwise, a tuple + of the different values is returned. + + Args: + + * other (metadata): + A metadata instance of the same type. + + Returns: + Metadata instance. + + """ + if ( + not hasattr(other, "__class__") + or other.__class__ is not self.__class__ + ): + emsg = "Cannot differ {!r} with {!r}." + raise ValueError(emsg.format(self.__class__.__name__, type(other))) + + if LENIENT("difference", cls=self.__class__): + print("lenient difference") + values = self._difference_lenient(other) + else: + print("strict difference") + + def func(field): + left = getattr(self, field) + right = getattr(other, field) + if self._is_attributes(field, left, right): + result = self._difference_strict_attributes(left, right) + else: + result = None if left == right else (left, right) + return result + + # Note that, we use "_fields" not "_members". + values = [func(field) for field in self._fields] + + return self.__class__(*values) def name(self, default=None, token=False): """ @@ -154,69 +407,26 @@ def _check(item): return result - @lenient_service - def __eq__(self, other): - result = NotImplemented - if isinstance(other, self.__class__): - if LENIENT(self.__eq__): - # Perform "lenient" comparison. - result = self._compare(other) - else: - # Perform "strict" comparison. - result = super().__eq__(other) - - return result - - def __lt__(self, other): - # - # Support Python2 behaviour for a "<" operation involving a - # "NoneType" operand. Require to at least implement this comparison - # operator to support sorting of instances. - # - if not isinstance(other, self.__class__): - return NotImplemented - - def _sort_key(item): - keys = [] - for field in item._fields: - value = getattr(item, field) - keys.extend((value is not None, value)) - return tuple(keys) - - return _sort_key(self) < _sort_key(other) - - def __ne__(self, other): - result = self.__eq__(other) - if result is not NotImplemented: - result = not result - - return result - - def _compare(self, other): + @classmethod + def token(cls, name): """ - Support lenient metadata equality for coordinates. + Determine whether the provided name is a valid NetCDF name and thus + safe to represent a single parsable token. Args: - * other (CoordMetadata): - The other coordinate metadata participating in the lenient - comparison. + * name: + The string name to verify Returns: - Boolean. + The provided name if valid, otherwise None. """ - result = False - # Use the "name" method to leniently compare "standard_name", - # "long_name" and "var_name" in a well defined way. - if self.name() == other.name(): - # Perform "strict" comparison for "units". - result = self.units == other.units - # Perform "lenient" comparison for "attributes". - # This effectively means they are always equal, therefore there is - # no further work to do here. + if name is not None: + result = _TOKEN_PARSE.match(name) + name = result if result is None else name - return result + return name class AncillaryVariableMetadata(BaseMetadata): @@ -238,6 +448,76 @@ class CellMeasureMetadata(BaseMetadata): __slots__ = () + def _combine_lenient(self, other): + """ + Perform lenient metadata member combination for cell measures. + + Args: + + * other (CellMeasureMetadata): + The other cell measure metadata participating in the lenient + combination. + + Returns: + A list of combined metadata member values. + + """ + # Perform "strict" combination for "measure". + value = self.measure if self.measure == other.measure else None + # Perform lenient combination of the other parent members. + result = super()._combine_lenient(other) + result.append(value) + + return result + + def _compare_lenient(self, other): + """ + Perform lenient metadata equality for cell measures. + + Args: + + * other (CellMeasureMetadata): + The other cell measure metadata participating in the lenient + comparison. + + Returns: + Boolean. + + """ + # Perform "strict" comparison for "measure". + result = self.measure == other.measure + if result: + # Perform lenient comparison of the other parent members. + result = super()._compare_lenient(other) + + return result + + def _difference_lenient(self, other): + """ + Perform lenient metadata member difference for cell measures. + + Args: + + * other (CellMeasureMetadata): + The other cell measure metadata participating in the lenient + difference. + + Returns: + A list of difference metadata member values. + + """ + # Perform "strict" difference for "measure". + value = ( + None + if self.measure == other.measure + else (self.measure, other.measure) + ) + # Perform lenient difference of the other parent members. + result = super()._difference_lenient(other) + result.append(value) + + return result + class CoordMetadata(BaseMetadata): """ @@ -249,9 +529,35 @@ class CoordMetadata(BaseMetadata): __slots__ = () - def _compare(self, other): + def _combine_lenient(self, other): """ - Support lenient metadata equality for coordinates. + Perform lenient metadata member combination for coordinates. + + Args: + + * other (CoordMetadata): + The other coordinate metadata participating in the lenient + combination. + + Returns: + A list of combined metadata member values. + + """ + # Perform "strict" combination for "coord_system" and "climatological". + def func(field): + value = getattr(self, field) + return value if value == getattr(other, field) else None + + values = [func(field) for field in self._members] + # Perform lenient combination of the other parent members. + result = super()._combine_lenient(other) + result.extend(values) + + return result + + def _compare_lenient(self, other): + """ + Perform lenient metadata equality for coordinates. Args: @@ -269,21 +575,35 @@ def _compare(self, other): and self.climatological == other.climatological ) if result: - # Perform lenient comparison of the other members. - result = super()._compare(other) + # Perform lenient comparison of the other parent members. + result = super()._compare_lenient(other) return result - @lenient_service - def __eq__(self, other): - result = NotImplemented - if isinstance(other, self.__class__): - if LENIENT(self.__eq__): - # Perform "lenient" comparison. - result = self._compare(other) - else: - # Perform "strict" comparison. - result = super().__eq__(other) + def _difference_lenient(self, other): + """ + Perform lenient metadata member difference for coordinates. + + Args: + + * other (CoordMetadata): + The other coordinate metadata participating in the lenient + difference. + + Returns: + A list of difference metadata member values. + + """ + # Perform "strict" difference for "coord_system" and "climatological". + def func(field): + left = getattr(self, field) + right = getattr(other, field) + return None if left == right else (left, right) + + values = [func(field) for field in self._members] + # Perform lenient difference of the other parent members. + result = super()._difference_lenient(other) + result.extend(values) return result @@ -298,32 +618,73 @@ class CubeMetadata(BaseMetadata): __slots__ = () - @wraps(BaseMetadata.name) - def name(self, default=None, token=False): - def _check(item): - return self.token(item) if token else item + def _combine_lenient(self, other): + """ + Perform lenient metadata member combination for cubes. - default = self.DEFAULT_NAME if default is None else default + Args: - # Defensive enforcement of attributes being a dictionary. - if not isinstance(self.attributes, Mapping): - try: - self.attributes = dict() - except AttributeError: - emsg = "Invalid '{}.attributes' member, must be a mapping." - raise AttributeError(emsg.format(self.__class__.__name__)) + * other (CubeMeatadata): + The other cube metadata participating in the lenient combination. - result = ( - _check(self.standard_name) - or _check(self.long_name) - or _check(self.var_name) - or _check(str(self.attributes.get("STASH", ""))) - or _check(default) + Returns: + A list of combined metadata member values. + + """ + # Perform "strict" combination for "cell_methods". + value = ( + self.cell_methods + if self.cell_methods == other.cell_methods + else None ) + # Perform lenient combination of the other parent members. + result = super()._combine_lenient(other) + result.append(value) - if token and result is None: - emsg = "Cannot retrieve a valid name token from {!r}" - raise ValueError(emsg.format(self)) + return result + + def _compare_lenient(self, other): + """ + Perform lenient metadata equality for cubes. + + Args: + + * other (CubeMetadata): + The other cube metadata participating in the lenient comparison. + + Returns: + Boolean. + + """ + # Perform "strict" comparison for "cell_methods". + result = self.cell_methods == other.cell_methods + if result: + result = super()._compare_lenient(other) + + return result + + def _difference_lenient(self, other): + """ + Perform lenient metadata member difference for cubes. + + Args: + + * other (CubeMetadata): + The other cube metadata participating in the lenient difference. + + Returns: + A list of difference metadata member values. + + """ + # Perform "strict" difference for "cell_methods". + value = ( + None + if self.cell_methods == other.cell_methods + else (self.cell_methods, other.cell_methods) + ) + # Perform lenient difference of the other parent members. + result = super()._difference_lenient(other) + result.append(value) return result @@ -354,6 +715,35 @@ def _names(self): return (standard_name, long_name, var_name, stash_name) + @wraps(BaseMetadata.name) + def name(self, default=None, token=False): + def _check(item): + return self.token(item) if token else item + + default = self.DEFAULT_NAME if default is None else default + + # Defensive enforcement of attributes being a dictionary. + if not isinstance(self.attributes, Mapping): + try: + self.attributes = dict() + except AttributeError: + emsg = "Invalid '{}.attributes' member, must be a mapping." + raise AttributeError(emsg.format(self.__class__.__name__)) + + result = ( + _check(self.standard_name) + or _check(self.long_name) + or _check(self.var_name) + or _check(str(self.attributes.get("STASH", ""))) + or _check(default) + ) + + if token and result is None: + emsg = "Cannot retrieve a valid name token from {!r}" + raise ValueError(emsg.format(self)) + + return result + def MetadataManagerFactory(cls, **kwargs): """ From 3cf88080bcdf0cc3a548162cda5dd18fd222537f Mon Sep 17 00:00:00 2001 From: Bill Little Date: Sat, 14 Dec 2019 00:10:16 +0000 Subject: [PATCH 04/45] explicit inherited lenient_service + support equal convenience --- lib/iris/common/_lenient.py | 57 +++++----------------- lib/iris/common/metadata.py | 94 +++++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 48 deletions(-) diff --git a/lib/iris/common/_lenient.py b/lib/iris/common/_lenient.py index 3cd83fbe41..f964dc2482 100644 --- a/lib/iris/common/_lenient.py +++ b/lib/iris/common/_lenient.py @@ -8,7 +8,6 @@ from contextlib import contextmanager from functools import wraps from inspect import getmodule -from itertools import product import threading @@ -44,34 +43,26 @@ def lenient_inner(*args, **kwargs): return lenient_inner -def qualname(func, cls=None): +def qualname(func): """ Return the fully qualified function/method string name. Args: - * func (callable/string): - Callable function/method. The fully qualified name of the callable - function/method is determined, otherwise the string name is used - instead. - - Kwargs: - - * cls (class): - If provided, the class is used to qualify the string name. + * func (callable): + Callable function/method. Non-callable arguments are simply + passed through. .. note:: Inherited methods will be qualified with the base class that defines the method. """ - if isinstance(func, str): - result = func - if cls is not None: - result = "{}.{}.{}".format(cls.__module__, cls.__name__, func) - else: + result = func + if callable(func): module = getmodule(func) result = f"{module.__name__}.{func.__qualname__}" + print(result) return result @@ -125,23 +116,6 @@ def __init__(self): # Currently executing lenient client at runtime. self.__dict__["active"] = None - # Define lenient services. - # Require to be explicit here for subclass methods that inherit parent - # class behaviour as a service for free. - classes = [ - "AncillaryVariableMetadata", - "CellMeasureMetadata", - "CoordMetadata", - "CubeMetadata", - ] - methods = [ - "__eq__", - "combine", - "difference", - ] - for cls, method in product(classes, methods): - self.__dict__[f"iris.common.metadata.{cls}.{method}"] = True - # Define lenient client/service relationships. # client = "iris.analysis.maths.add" # services = ("iris.common.metadata.CoordMetadata.__eq__",) @@ -152,29 +126,22 @@ def __init__(self): services = ("iris.common.metadata.CoordMetadata.__eq__",) self.__dict__[client] = services - def __call__(self, func, cls=None): + def __call__(self, func): """ Determine whether it is valid for the function/method to provide a lenient service at runtime to the actively executing lenient client. Args: - * func (callable/string): - Callable function/method providing the lenient service. The fully - qualified name of the callable function/method is determined, - otherwise the string name is used instead. - - Kwargs: - - * cls (class): - If provided, the class is used to qualify the string name. + * func (callable): + Callable function/method providing the lenient service. Returns: Boolean. """ result = False - service = qualname(func, cls=cls) + service = qualname(func) if service in self and self.__dict__[service]: active = self.__dict__["active"] if active is not None and active in self: @@ -259,7 +226,7 @@ def context(self, *args, **kwargs): if active is None: active = "context" self.__dict__["active"] = active - self.__dict__[active] = args + self.__dict__[active] = tuple([qualname(arg) for arg in args]) try: yield finally: diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 688711acb2..f2b4eb11a3 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -93,13 +93,16 @@ class BaseMetadata(metaclass=_NamedTupleMeta): @lenient_service def __eq__(self, other): + """hello world""" result = NotImplemented if hasattr(other, "__class__") and other.__class__ is self.__class__: - if LENIENT("__eq__", cls=self.__class__): + if LENIENT(self.__eq__) or LENIENT(self.equal): # Perform "lenient" comparison. + print("lenient __eq__") result = self._compare_lenient(other) else: # Perform "strict" comparison. + print("strict __eq__") result = super().__eq__(other) return result @@ -304,7 +307,7 @@ def combine(self, other): emsg = "Cannot combine {!r} with {!r}." raise ValueError(emsg.format(self.__class__.__name__, type(other))) - if LENIENT("combine", cls=self.__class__): + if LENIENT(self.combine): print("lenient combine") values = self._combine_lenient(other) else: @@ -345,7 +348,7 @@ def difference(self, other): emsg = "Cannot differ {!r} with {!r}." raise ValueError(emsg.format(self.__class__.__name__, type(other))) - if LENIENT("difference", cls=self.__class__): + if LENIENT(self.difference): print("lenient difference") values = self._difference_lenient(other) else: @@ -365,6 +368,11 @@ def func(field): return self.__class__(*values) + @wraps(__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other): + return self.__eq__(other) + def name(self, default=None, token=False): """ Returns a string name representing the identity of the metadata. @@ -437,6 +445,26 @@ class AncillaryVariableMetadata(BaseMetadata): __slots__ = () + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def __eq__(self, other): + return super().__eq__(other) + + @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) + @lenient_service + def combine(self, other): + return super().combine(other) + + @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) + @lenient_service + def difference(self, other): + return super().difference(other) + + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other): + return self.__eq__(other) + class CellMeasureMetadata(BaseMetadata): """ @@ -448,6 +476,11 @@ class CellMeasureMetadata(BaseMetadata): __slots__ = () + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def __eq__(self, other): + return super().__eq__(other) + def _combine_lenient(self, other): """ Perform lenient metadata member combination for cell measures. @@ -518,6 +551,21 @@ def _difference_lenient(self, other): return result + @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) + @lenient_service + def combine(self, other): + return super().combine(other) + + @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) + @lenient_service + def difference(self, other): + return super().difference(other) + + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other): + return self.__eq__(other) + class CoordMetadata(BaseMetadata): """ @@ -529,6 +577,11 @@ class CoordMetadata(BaseMetadata): __slots__ = () + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def __eq__(self, other): + return super().__eq__(other) + def _combine_lenient(self, other): """ Perform lenient metadata member combination for coordinates. @@ -607,6 +660,21 @@ def func(field): return result + @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) + @lenient_service + def combine(self, other): + return super().combine(other) + + @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) + @lenient_service + def difference(self, other): + return super().difference(other) + + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other): + return self.__eq__(other) + class CubeMetadata(BaseMetadata): """ @@ -618,6 +686,11 @@ class CubeMetadata(BaseMetadata): __slots__ = () + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def __eq__(self, other): + return super().__eq__(other) + def _combine_lenient(self, other): """ Perform lenient metadata member combination for cubes. @@ -715,6 +788,21 @@ def _names(self): return (standard_name, long_name, var_name, stash_name) + @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) + @lenient_service + def combine(self, other): + return super().combine(other) + + @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) + @lenient_service + def difference(self, other): + return super().difference(other) + + @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @lenient_service + def equal(self, other): + return self.__eq__(other) + @wraps(BaseMetadata.name) def name(self, default=None, token=False): def _check(item): From d0079e93305cb6bd4589486c3746451f91d257cf Mon Sep 17 00:00:00 2001 From: Bill Little Date: Sat, 14 Dec 2019 01:48:11 +0000 Subject: [PATCH 05/45] fix attributes difference + lenient kwargs --- lib/iris/common/_lenient.py | 7 +- lib/iris/common/metadata.py | 248 +++++++++++++++++++++++++++--------- 2 files changed, 188 insertions(+), 67 deletions(-) diff --git a/lib/iris/common/_lenient.py b/lib/iris/common/_lenient.py index f964dc2482..52a9af7e92 100644 --- a/lib/iris/common/_lenient.py +++ b/lib/iris/common/_lenient.py @@ -62,7 +62,6 @@ def qualname(func): if callable(func): module = getmodule(func) result = f"{module.__name__}.{func.__qualname__}" - print(result) return result @@ -122,9 +121,9 @@ def __init__(self): # self.__dict__[client] = services # XXX: testing... - client = "__main__.myfunc" - services = ("iris.common.metadata.CoordMetadata.__eq__",) - self.__dict__[client] = services + # client = "__main__.myfunc" + # services = ("iris.common.metadata.CoordMetadata.__eq__",) + # self.__dict__[client] = services def __call__(self, func): """ diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index f2b4eb11a3..d4f669959b 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -10,7 +10,7 @@ from functools import wraps import re -from ._lenient import LENIENT, lenient_service +from ._lenient import LENIENT, lenient_service, qualname __all__ = [ @@ -93,7 +93,18 @@ class BaseMetadata(metaclass=_NamedTupleMeta): @lenient_service def __eq__(self, other): - """hello world""" + """ + Determine whether the associated metadata members are equivalent. + + Args: + + * other (metadata): + A metadata instance of the same type. + + Returns: + Boolean. + + """ result = NotImplemented if hasattr(other, "__class__") and other.__class__ is self.__class__: if LENIENT(self.__eq__) or LENIENT(self.equal): @@ -102,7 +113,6 @@ def __eq__(self, other): result = self._compare_lenient(other) else: # Perform "strict" comparison. - print("strict __eq__") result = super().__eq__(other) return result @@ -131,6 +141,23 @@ def __ne__(self, other): return result + def _combine(self, other): + """Perform associated metadata member combination.""" + if LENIENT(self.combine): + print("lenient combine") + values = self._combine_lenient(other) + else: + print("strict combine") + + def func(field): + value = getattr(self, field) + return value if value == getattr(other, field) else None + + # Note that, we use "_fields" not "_members". + values = [func(field) for field in self._fields] + + return values + def _combine_lenient(self, other): """ Perform lenient metadata member combination. @@ -214,6 +241,28 @@ def _compare_lenient(self, other): return result + def _difference(self, other): + """Perform associated metadata member difference.""" + if LENIENT(self.difference): + print("lenient difference") + values = self._difference_lenient(other) + else: + print("strict difference") + + def func(field): + left = getattr(self, field) + right = getattr(other, field) + if self._is_attributes(field, left, right): + result = self._difference_strict_attributes(left, right) + else: + result = None if left == right else (left, right) + return result + + # Note that, we use "_fields" not "_members". + values = [func(field) for field in self._fields] + + return values + def _difference_lenient(self, other): """ Perform lenient metadata member difference. @@ -262,7 +311,12 @@ def _difference_lenient_attributes(left, right): [dsleft.pop(key) for key in list(dsleft.keys()) if key not in keys] [dsright.pop(key) for key in list(dsright.keys()) if key not in keys] - return (dsleft, dsright) + if not bool(dsleft) and not bool(dsright): + result = None + else: + result = (dsleft, dsright) + + return result @staticmethod def _difference_strict_attributes(left, right): @@ -274,7 +328,12 @@ def _difference_strict_attributes(left, right): # Items in sright different from sleft. dsright = dict(sright - sleft) - return (dsleft, dsright) + if not bool(dsleft) and not bool(dsright): + result = None + else: + result = (dsleft, dsright) + + return result @staticmethod def _is_attributes(field, left, right): @@ -286,7 +345,7 @@ def _is_attributes(field, left, right): ) @lenient_service - def combine(self, other): + def combine(self, other, lenient=None): """ Return a new metadata instance created by combining each of the associated metadata members. @@ -296,6 +355,12 @@ def combine(self, other): * other (metadata): A metadata instance of the same type. + Kwargs: + + * lenient (boolean): + Enable/disable lenient combination. The default is to automatically + detect whether this lenient operation is enabled. + Returns: Metadata instance. @@ -307,23 +372,21 @@ def combine(self, other): emsg = "Cannot combine {!r} with {!r}." raise ValueError(emsg.format(self.__class__.__name__, type(other))) - if LENIENT(self.combine): - print("lenient combine") - values = self._combine_lenient(other) + if lenient is None: + values = self._combine(other) else: - print("strict combine") - - def func(field): - value = getattr(self, field) - return value if value == getattr(other, field) else None + if lenient: + args, kwargs = (qualname(self.combine),), dict() + else: + args, kwargs = (), {qualname(self.combine): False} - # Note that, we use "_fields" not "_members". - values = [func(field) for field in self._fields] + with LENIENT.context(*args, **kwargs): + values = self._combine(other) return self.__class__(*values) @lenient_service - def difference(self, other): + def difference(self, other, lenient=None): """ Return a new metadata instance created by performing a difference comparison between each of the associated metadata members. @@ -337,6 +400,12 @@ def difference(self, other): * other (metadata): A metadata instance of the same type. + Kwargs: + + * lenient (boolean): + Enable/disable lenient difference. The default is to automatically + detect whether this lenient operation is enabled. + Returns: Metadata instance. @@ -348,30 +417,51 @@ def difference(self, other): emsg = "Cannot differ {!r} with {!r}." raise ValueError(emsg.format(self.__class__.__name__, type(other))) - if LENIENT(self.difference): - print("lenient difference") - values = self._difference_lenient(other) + if lenient is None: + values = self._difference(other) else: - print("strict difference") + if lenient: + args, kwargs = (qualname(self.difference),), dict() + else: + args, kwargs = (), {qualname(self.difference): False} - def func(field): - left = getattr(self, field) - right = getattr(other, field) - if self._is_attributes(field, left, right): - result = self._difference_strict_attributes(left, right) - else: - result = None if left == right else (left, right) - return result - - # Note that, we use "_fields" not "_members". - values = [func(field) for field in self._fields] + with LENIENT.context(*args, **kwargs): + values = self._difference(other) return self.__class__(*values) - @wraps(__eq__, assigned=("__doc__",), updated=()) @lenient_service - def equal(self, other): - return self.__eq__(other) + def equal(self, other, lenient=None): + """ + Determine whether the associated metadata members are equivalent. + + Args: + + * other (metadata): + A metadata instance of the same type. + + Kwargs: + + * lenient (boolean): + Enable/disable lenient equivalence. The default is to automatically + detect whether this lenient operation is enabled. + + Returns: + Boolean. + + """ + if lenient is None: + result = self.__eq__(other) + else: + if lenient: + args, kwargs = (qualname(self.equal),), dict() + else: + args, kwargs = (), {qualname(self.equal): False} + + with LENIENT.context(*args, **kwargs): + result = self.__eq__(other) + + return result def name(self, default=None, token=False): """ @@ -452,18 +542,18 @@ def __eq__(self, other): @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) @lenient_service - def combine(self, other): - return super().combine(other) + def combine(self, other, lenient=None): + return super().combine(other, lenient=lenient) @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) @lenient_service - def difference(self, other): - return super().difference(other) + def difference(self, other, lenient=None): + return super().difference(other, lenient=lenient) - @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) @lenient_service - def equal(self, other): - return self.__eq__(other) + def equal(self, other, lenient=None): + return super().equal(other, lenient=lenient) class CellMeasureMetadata(BaseMetadata): @@ -553,18 +643,18 @@ def _difference_lenient(self, other): @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) @lenient_service - def combine(self, other): - return super().combine(other) + def combine(self, other, lenient=None): + return super().combine(other, lenient=lenient) @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) @lenient_service - def difference(self, other): - return super().difference(other) + def difference(self, other, lenient=None): + return super().difference(other, lenient=lenient) - @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) @lenient_service - def equal(self, other): - return self.__eq__(other) + def equal(self, other, lenient=None): + return super().equal(other, lenient=lenient) class CoordMetadata(BaseMetadata): @@ -662,18 +752,18 @@ def func(field): @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) @lenient_service - def combine(self, other): - return super().combine(other) + def combine(self, other, lenient=None): + return super().combine(other, lenient=lenient) @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) @lenient_service - def difference(self, other): - return super().difference(other) + def difference(self, other, lenient=None): + return super().difference(other, lenient=lenient) - @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) @lenient_service - def equal(self, other): - return self.__eq__(other) + def equal(self, other, lenient=None): + return super().equal(other, lenient=lenient) class CubeMetadata(BaseMetadata): @@ -790,18 +880,18 @@ def _names(self): @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) @lenient_service - def combine(self, other): - return super().combine(other) + def combine(self, other, lenient=None): + return super().combine(other, lenient=lenient) @wraps(BaseMetadata.difference, assigned=("__doc__",), updated=()) @lenient_service - def difference(self, other): - return super().difference(other) + def difference(self, other, lenient=None): + return super().difference(other, lenient=lenient) - @wraps(BaseMetadata.__eq__, assigned=("__doc__",), updated=()) + @wraps(BaseMetadata.equal, assigned=("__doc__",), updated=()) @lenient_service - def equal(self, other): - return self.__eq__(other) + def equal(self, other, lenient=None): + return super().equal(other, lenient=lenient) @wraps(BaseMetadata.name) def name(self, default=None, token=False): @@ -968,3 +1058,35 @@ def values(self): metadata = Metadata(cls, **kwargs) return metadata + + +COMBINE = ( + AncillaryVariableMetadata.combine, + BaseMetadata.combine, + CellMeasureMetadata.combine, + CoordMetadata.combine, + CubeMetadata.combine, +) + + +DIFFERENCE = ( + AncillaryVariableMetadata.difference, + BaseMetadata.difference, + CellMeasureMetadata.difference, + CoordMetadata.difference, + CubeMetadata.difference, +) + + +EQUAL = ( + AncillaryVariableMetadata.__eq__, + AncillaryVariableMetadata.equal, + BaseMetadata.__eq__, + BaseMetadata.equal, + CellMeasureMetadata.__eq__, + CellMeasureMetadata.equal, + CoordMetadata.__eq__, + CoordMetadata.equal, + CubeMetadata.__eq__, + CubeMetadata.equal, +) From a1da4ca1a9cef26579ca4238a892d82244d72c3a Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 9 Jun 2020 11:56:05 +0100 Subject: [PATCH 06/45] make lenient public + minor tidy --- lib/iris/analysis/maths.py | 5 -- lib/iris/common/__init__.py | 1 + lib/iris/common/{_lenient.py => lenient.py} | 66 ++++++++++++--------- lib/iris/common/metadata.py | 13 +++- 4 files changed, 49 insertions(+), 36 deletions(-) rename lib/iris/common/{_lenient.py => lenient.py} (94%) diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index 6ee5d4adaf..0de97b02f3 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -19,7 +19,6 @@ from numpy import ma import iris.analysis -from iris.common._lenient import lenient_client import iris.coords import iris.cube import iris.exceptions @@ -217,7 +216,6 @@ def _assert_matching_units(cube, other, operation_name): raise iris.exceptions.NotYetImplementedError(msg) -@lenient_client def add(cube, other, dim=None, in_place=False): """ Calculate the sum of two cubes, or the sum of a cube and a @@ -263,7 +261,6 @@ def add(cube, other, dim=None, in_place=False): ) -@lenient_client def subtract(cube, other, dim=None, in_place=False): """ Calculate the difference between two cubes, or the difference between @@ -383,7 +380,6 @@ def _add_subtract_common( return new_cube -@lenient_client def multiply(cube, other, dim=None, in_place=False): """ Calculate the product of a cube and another cube or coordinate. @@ -479,7 +475,6 @@ def _inplace_common_checks(cube, other, math_op): ) -@lenient_client def divide(cube, other, dim=None, in_place=False): """ Calculate the division of a cube by a cube or coordinate. diff --git a/lib/iris/common/__init__.py b/lib/iris/common/__init__.py index 3f25865a01..52759dac8e 100644 --- a/lib/iris/common/__init__.py +++ b/lib/iris/common/__init__.py @@ -5,5 +5,6 @@ # licensing details. +from .lenient import * from .metadata import * from .mixin import * diff --git a/lib/iris/common/_lenient.py b/lib/iris/common/lenient.py similarity index 94% rename from lib/iris/common/_lenient.py rename to lib/iris/common/lenient.py index 52a9af7e92..64d7bb8b7b 100644 --- a/lib/iris/common/_lenient.py +++ b/lib/iris/common/lenient.py @@ -11,6 +11,14 @@ import threading +__all__ = [ + "lenient_client", + "lenient_service", + "qualname", + "LENIENT", +] + + # TODO: allow *args to specify the ephemeral services that the client wishes to # use which are then unpacked in the LENIENT.context def lenient_client(func): @@ -43,29 +51,6 @@ def lenient_inner(*args, **kwargs): return lenient_inner -def qualname(func): - """ - Return the fully qualified function/method string name. - - Args: - - * func (callable): - Callable function/method. Non-callable arguments are simply - passed through. - - .. note:: - Inherited methods will be qualified with the base class that - defines the method. - - """ - result = func - if callable(func): - module = getmodule(func) - result = f"{module.__name__}.{func.__qualname__}" - - return result - - def lenient_service(func): """ Decorator that allows a function/method to declare that it supports lenient @@ -80,7 +65,7 @@ def lenient_service(func): Closure wrapped function/method. """ - LENIENT.register(qualname(func)) + LENIENT.register(func) @wraps(func) def register_inner(*args, **kwargs): @@ -94,6 +79,29 @@ def register_inner(*args, **kwargs): return register_inner +def qualname(func): + """ + Return the fully qualified function/method string name. + + Args: + + * func (callable): + Callable function/method. Non-callable arguments are simply + passed through. + + .. note:: + Inherited methods will be qualified with the base class that + defines the method. + + """ + result = func + if callable(func): + module = getmodule(func) + result = f"{module.__name__}.{func.__qualname__}" + + return result + + class Lenient(threading.local): def __init__(self): """ @@ -239,10 +247,11 @@ def register(self, name): Args: - * name (string): - Fully qualified string name of the function/method. + * name (callable/string): + A function/method or fully qualified string name of the function/method. """ + name = qualname(name) self.__dict__[name] = True def unregister(self, name): @@ -251,10 +260,11 @@ def unregister(self, name): Args: - * name (string): - Fully qualified string name of the function/method. + * name (callable/string): + A function/method or fully qualified string name of the function/method. """ + name = qualname(name) if name in self.__dict__: self.__dict__[name] = False else: diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index d4f669959b..abefeea07a 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -10,10 +10,13 @@ from functools import wraps import re -from ._lenient import LENIENT, lenient_service, qualname +from .lenient import LENIENT, lenient_service, qualname __all__ = [ + "COMBINE", + "DIFFERENCE", + "EQUAL", "AncillaryVariableMetadata", "BaseMetadata", "CellMeasureMetadata", @@ -876,7 +879,7 @@ def _names(self): if stash_name is not None: stash_name = str(stash_name) - return (standard_name, long_name, var_name, stash_name) + return standard_name, long_name, var_name, stash_name @wraps(BaseMetadata.combine, assigned=("__doc__",), updated=()) @lenient_service @@ -923,6 +926,7 @@ def _check(item): return result +# TODO: rename to metadata_manager_factory def MetadataManagerFactory(cls, **kwargs): """ A class instance factory function responsible for manufacturing @@ -991,7 +995,7 @@ def __reduce__(self): instance, and dump and load instance state successfully. """ - return (MetadataManagerFactory, (self.cls,), self.__getstate__()) + return MetadataManagerFactory, (self.cls,), self.__getstate__() def __repr__(self): args = ", ".join( @@ -1060,6 +1064,7 @@ def values(self): return metadata +#: Convenience collection of lenient metadata combine services. COMBINE = ( AncillaryVariableMetadata.combine, BaseMetadata.combine, @@ -1069,6 +1074,7 @@ def values(self): ) +#: Convenience collection of lenient metadata difference services. DIFFERENCE = ( AncillaryVariableMetadata.difference, BaseMetadata.difference, @@ -1078,6 +1084,7 @@ def values(self): ) +#: Convenience collection of lenient metadata equality services. EQUAL = ( AncillaryVariableMetadata.__eq__, AncillaryVariableMetadata.equal, From e3bca165861b786251ba32a8db2eb9d5a887fc99 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 9 Jun 2020 12:19:45 +0100 Subject: [PATCH 07/45] rename MetadataManagerFactory to metadata_manager_factory --- lib/iris/aux_factory.py | 18 +++++----- lib/iris/common/metadata.py | 7 ++-- lib/iris/coords.py | 10 +++--- lib/iris/cube.py | 4 +-- ...ry.py => test_metadata_manager_factory.py} | 34 +++++++++---------- 5 files changed, 36 insertions(+), 37 deletions(-) rename lib/iris/tests/unit/common/metadata/{test_MetadataManagerFactory.py => test_metadata_manager_factory.py} (85%) diff --git a/lib/iris/aux_factory.py b/lib/iris/aux_factory.py index 1f134f65c0..d5ccdc28d3 100644 --- a/lib/iris/aux_factory.py +++ b/lib/iris/aux_factory.py @@ -14,7 +14,7 @@ import dask.array as da import numpy as np -from iris.common import CFVariableMixin, CoordMetadata, MetadataManagerFactory +from iris.common import CFVariableMixin, CoordMetadata, metadata_manager_factory import iris.coords @@ -35,7 +35,7 @@ class AuxCoordFactory(CFVariableMixin, metaclass=ABCMeta): def __init__(self): # Configure the metadata manager. if not hasattr(self, "_metadata_manager"): - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) #: Descriptive name of the coordinate made by the factory self.long_name = None @@ -385,7 +385,7 @@ def __init__(self, delta=None, sigma=None, orography=None): """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__() if delta and delta.nbounds not in (0, 2): @@ -574,7 +574,7 @@ def __init__(self, delta=None, sigma=None, surface_air_pressure=None): """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__() # Check that provided coords meet necessary conditions. @@ -779,7 +779,7 @@ def __init__( """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__() # Check that provided coordinates meet necessary conditions. @@ -1080,7 +1080,7 @@ def __init__(self, sigma=None, eta=None, depth=None): """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__() # Check that provided coordinates meet necessary conditions. @@ -1263,7 +1263,7 @@ def __init__(self, s=None, c=None, eta=None, depth=None, depth_c=None): """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__() # Check that provided coordinates meet necessary conditions. @@ -1486,7 +1486,7 @@ def __init__( """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__() # Check that provided coordinates meet necessary conditions. @@ -1704,7 +1704,7 @@ def __init__(self, s=None, c=None, eta=None, depth=None, depth_c=None): """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__() # Check that provided coordinates meet necessary conditions. diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index abefeea07a..ae8637b029 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -22,7 +22,7 @@ "CellMeasureMetadata", "CoordMetadata", "CubeMetadata", - "MetadataManagerFactory", + "metadata_manager_factory", ] @@ -926,8 +926,7 @@ def _check(item): return result -# TODO: rename to metadata_manager_factory -def MetadataManagerFactory(cls, **kwargs): +def metadata_manager_factory(cls, **kwargs): """ A class instance factory function responsible for manufacturing metadata instances dynamically at runtime. @@ -995,7 +994,7 @@ def __reduce__(self): instance, and dump and load instance state successfully. """ - return MetadataManagerFactory, (self.cls,), self.__getstate__() + return metadata_manager_factory, (self.cls,), self.__getstate__() def __repr__(self): args = ", ".join( diff --git a/lib/iris/coords.py b/lib/iris/coords.py index e5574b3388..e0537a12a1 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -31,7 +31,7 @@ CFVariableMixin, CellMeasureMetadata, CoordMetadata, - MetadataManagerFactory, + metadata_manager_factory, ) import iris.exceptions import iris.time @@ -99,7 +99,7 @@ def __init__( # Configure the metadata manager. if not hasattr(self, "_metadata_manager"): - self._metadata_manager = MetadataManagerFactory(BaseMetadata) + self._metadata_manager = metadata_manager_factory(BaseMetadata) #: CF standard name of the quantity that the metadata represents. self.standard_name = standard_name @@ -710,7 +710,7 @@ def __init__( """ # Configure the metadata manager. if not hasattr(self, "_metadata_manager"): - self._metadata_manager = MetadataManagerFactory( + self._metadata_manager = metadata_manager_factory( AncillaryVariableMetadata ) @@ -822,7 +822,7 @@ def __init__( """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CellMeasureMetadata) + self._metadata_manager = metadata_manager_factory(CellMeasureMetadata) super().__init__( data=data, @@ -1321,7 +1321,7 @@ def __init__( """ # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CoordMetadata) + self._metadata_manager = metadata_manager_factory(CoordMetadata) super().__init__( values=points, diff --git a/lib/iris/cube.py b/lib/iris/cube.py index a1359911fe..490486a998 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -42,7 +42,7 @@ CFVariableMixin, CoordMetadata, CubeMetadata, - MetadataManagerFactory, + metadata_manager_factory, ) import iris.coord_systems import iris.coords @@ -748,7 +748,7 @@ def __init__( raise TypeError("Invalid data type: {!r}.".format(data)) # Configure the metadata manager. - self._metadata_manager = MetadataManagerFactory(CubeMetadata) + self._metadata_manager = metadata_manager_factory(CubeMetadata) # Initialise the cube data manager. self._data_manager = DataManager(data) diff --git a/lib/iris/tests/unit/common/metadata/test_MetadataManagerFactory.py b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py similarity index 85% rename from lib/iris/tests/unit/common/metadata/test_MetadataManagerFactory.py rename to lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py index bfc777cb0c..6678aca446 100644 --- a/lib/iris/tests/unit/common/metadata/test_MetadataManagerFactory.py +++ b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Unit tests for the :class:`iris.common.metadata.MetadataManagerFactory`. +Unit tests for the :func:`iris.common.metadata.metadata_manager_factory`. """ @@ -23,7 +23,7 @@ CellMeasureMetadata, CoordMetadata, CubeMetadata, - MetadataManagerFactory, + metadata_manager_factory, ) @@ -43,12 +43,12 @@ class Other: emsg = "Require a subclass of 'BaseMetadata'" with self.assertRaisesRegex(TypeError, emsg): - _ = MetadataManagerFactory(Other) + _ = metadata_manager_factory(Other) def test__kwargs_invalid(self): emsg = "Invalid 'BaseMetadata' field parameters, got 'wibble'." with self.assertRaisesRegex(ValueError, emsg): - MetadataManagerFactory(BaseMetadata, wibble="nope") + metadata_manager_factory(BaseMetadata, wibble="nope") class Test_instance(tests.IrisTest): @@ -71,7 +71,7 @@ def test__namespace(self): "values", ] for base in self.bases: - metadata = MetadataManagerFactory(base) + metadata = metadata_manager_factory(base) for name in namespace: self.assertTrue(hasattr(metadata, name)) if base is CubeMetadata: @@ -81,33 +81,33 @@ def test__namespace(self): def test__kwargs_default(self): for base in self.bases: kwargs = dict(zip(base._fields, [None] * len(base._fields))) - metadata = MetadataManagerFactory(base) + metadata = metadata_manager_factory(base) self.assertEqual(metadata.values._asdict(), kwargs) def test__kwargs(self): for base in self.bases: kwargs = dict(zip(base._fields, range(len(base._fields)))) - metadata = MetadataManagerFactory(base, **kwargs) + metadata = metadata_manager_factory(base, **kwargs) self.assertEqual(metadata.values._asdict(), kwargs) class Test_instance___eq__(tests.IrisTest): def setUp(self): - self.metadata = MetadataManagerFactory(BaseMetadata) + self.metadata = metadata_manager_factory(BaseMetadata) def test__not_implemented(self): self.assertNotEqual(self.metadata, 1) def test__not_is_cls(self): base = BaseMetadata - other = MetadataManagerFactory(base) + other = metadata_manager_factory(base) self.assertIs(other.cls, base) other.cls = CoordMetadata self.assertNotEqual(self.metadata, other) def test__not_values(self): standard_name = mock.sentinel.standard_name - other = MetadataManagerFactory( + other = metadata_manager_factory( BaseMetadata, standard_name=standard_name ) self.assertEqual(other.standard_name, standard_name) @@ -118,22 +118,22 @@ def test__not_values(self): self.assertNotEqual(self.metadata, other) def test__same_default(self): - other = MetadataManagerFactory(BaseMetadata) + other = metadata_manager_factory(BaseMetadata) self.assertEqual(self.metadata, other) def test__same(self): kwargs = dict( standard_name=1, long_name=2, var_name=3, units=4, attributes=5 ) - metadata = MetadataManagerFactory(BaseMetadata, **kwargs) - other = MetadataManagerFactory(BaseMetadata, **kwargs) + metadata = metadata_manager_factory(BaseMetadata, **kwargs) + other = metadata_manager_factory(BaseMetadata, **kwargs) self.assertEqual(metadata.values._asdict(), kwargs) self.assertEqual(metadata, other) class Test_instance____repr__(tests.IrisTest): def setUp(self): - self.metadata = MetadataManagerFactory(BaseMetadata) + self.metadata = metadata_manager_factory(BaseMetadata) def test(self): standard_name = mock.sentinel.standard_name @@ -169,7 +169,7 @@ def setUp(self): self.attributes, ) self.kwargs = dict(zip(BaseMetadata._fields, values)) - self.metadata = MetadataManagerFactory(BaseMetadata, **self.kwargs) + self.metadata = metadata_manager_factory(BaseMetadata, **self.kwargs) def test_pickle(self): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): @@ -188,7 +188,7 @@ def setUp(self): def test(self): for base in self.bases: fields = base._fields - metadata = MetadataManagerFactory(base) + metadata = metadata_manager_factory(base) self.assertEqual(metadata.fields, fields) for field in fields: hasattr(metadata, field) @@ -200,7 +200,7 @@ def setUp(self): def test(self): for base in self.bases: - metadata = MetadataManagerFactory(base) + metadata = metadata_manager_factory(base) result = metadata.values self.assertIsInstance(result, base) self.assertEqual(result._fields, base._fields) From 3d345d05bf017417ee5a2757466ec1d61388423b Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 10 Jun 2020 14:23:14 +0100 Subject: [PATCH 08/45] extend lenient_client decorator to support services registration --- lib/iris/common/lenient.py | 129 +++++++++++++++++++++++++----------- lib/iris/common/metadata.py | 17 +++-- 2 files changed, 103 insertions(+), 43 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 64d7bb8b7b..14013aadf6 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -19,36 +19,99 @@ ] -# TODO: allow *args to specify the ephemeral services that the client wishes to -# use which are then unpacked in the LENIENT.context -def lenient_client(func): +def lenient_client(*dargs, services=None): """ Decorator that allows a client function/method to declare at runtime that it is executing and requires lenient behaviour from a prior registered lenient service function/method. + This decorator supports being called with no arguments e.g., + + @lenient_client() + def func(): + pass + + This is equivalent to using it as a simple naked decorator e.g., + + @lenient_client + def func() + pass + + Alternatively, this decorator supports the lenient client explicitly + declaring the lenient services that it wishes to use e.g., + + @lenient_client(services=(service1, service2, ...) + def func(): + pass + Args: - * func (callable): - Callable function/method to be wrapped by the decorator. + * dargs (tuple of callable): + A tuple containing the callable lenient client function/method to be + wrapped by the decorator. This is automatically populated by Python + through the decorator interface. No argument requires to be manually + provided. + + Kwargs: + + * services (callable or str or iterable of callable/str) + Zero or more function/methods, or equivalent fully qualified string names, of + lenient service function/methods. Returns: Closure wrapped function/method. """ + ndargs = len(dargs) - @wraps(func) - def lenient_inner(*args, **kwargs): - """ - Closure wrapper function to register the wrapped function/method as - active at runtime before executing it. + if ndargs: + assert ndargs == 1, f"Invalid lenient client arguments, expecting 1 got {ndargs}." + assert callable(dargs[0]), f"Invalid lenient client argument, expecting callable." - """ - with LENIENT.context(active=qualname(func)): - result = func(*args, **kwargs) - return result + assert not(ndargs and services), f"Invalid lenient client arguments." + + if ndargs: + # The decorator has been used as a simple naked decorator. + func = dargs[0] - return lenient_inner + @wraps(func) + def lenient_inner(*args, **kwargs): + """ + Closure wrapper function to register the wrapped function/method + as active at runtime before executing it. + + """ + with LENIENT.context(active=qualname(func)): + result = func(*args, **kwargs) + return result + + result = lenient_inner + else: + # The decorator has been called with None, zero or more explicit lenient services. + if services is None: + services = () + + if isinstance(services, str) or not isinstance(services, Iterable): + services = (services,) + + def lenient_outer(func): + + @wraps(func) + def lenient_inner(*args, **kwargs): + """ + Closure wrapper function to register the wrapped function/method + as active at runtime before executing it. + + """ + with LENIENT.context(*services, active=qualname(func)): + result = func(*args, **kwargs) + return result + + return lenient_inner + + result = lenient_outer + + return result def lenient_service(func): @@ -56,6 +119,8 @@ def lenient_service(func): Decorator that allows a function/method to declare that it supports lenient behaviour. + Registration is at Python interpreter parse time. + Args: * func (callable): @@ -123,16 +188,6 @@ def __init__(self): # Currently executing lenient client at runtime. self.__dict__["active"] = None - # Define lenient client/service relationships. - # client = "iris.analysis.maths.add" - # services = ("iris.common.metadata.CoordMetadata.__eq__",) - # self.__dict__[client] = services - - # XXX: testing... - # client = "__main__.myfunc" - # services = ("iris.common.metadata.CoordMetadata.__eq__",) - # self.__dict__[client] = services - def __call__(self, func): """ Determine whether it is valid for the function/method to provide a @@ -140,8 +195,8 @@ def __call__(self, func): Args: - * func (callable): - Callable function/method providing the lenient service. + * func (callable or str): + A function/method or fully qualified string name of the function/method. Returns: Boolean. @@ -241,35 +296,35 @@ def context(self, *args, **kwargs): self.__dict__.clear() self.__dict__.update(current_state) - def register(self, name): + def register(self, func): """ Register the provided function/method as providing a lenient service. Args: - * name (callable/string): + * func (callable or str): A function/method or fully qualified string name of the function/method. """ - name = qualname(name) - self.__dict__[name] = True + func = qualname(func) + self.__dict__[func] = True - def unregister(self, name): + def unregister(self, func): """ Unregister the provided function/method as providing a lenient service. Args: - * name (callable/string): + * func (callable or str): A function/method or fully qualified string name of the function/method. """ - name = qualname(name) - if name in self.__dict__: - self.__dict__[name] = False + func = qualname(func) + if func in self.__dict__: + self.__dict__[func] = False else: cls = self.__class__.__name__ - emsg = f"Cannot unregister invalid {cls!r} service, got {name!r}." + emsg = f"Cannot unregister invalid {cls!r} service, got {func!r}." raise ValueError(emsg) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index ae8637b029..c60091b47e 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -14,9 +14,10 @@ __all__ = [ - "COMBINE", - "DIFFERENCE", - "EQUAL", + "SERVICES_COMBINE", + "SERVICES_DIFFERENCE", + "SERVICES_EQUAL", + "SERVICES", "AncillaryVariableMetadata", "BaseMetadata", "CellMeasureMetadata", @@ -1064,7 +1065,7 @@ def values(self): #: Convenience collection of lenient metadata combine services. -COMBINE = ( +SERVICES_COMBINE = ( AncillaryVariableMetadata.combine, BaseMetadata.combine, CellMeasureMetadata.combine, @@ -1074,7 +1075,7 @@ def values(self): #: Convenience collection of lenient metadata difference services. -DIFFERENCE = ( +SERVICES_DIFFERENCE = ( AncillaryVariableMetadata.difference, BaseMetadata.difference, CellMeasureMetadata.difference, @@ -1084,7 +1085,7 @@ def values(self): #: Convenience collection of lenient metadata equality services. -EQUAL = ( +SERVICES_EQUAL = ( AncillaryVariableMetadata.__eq__, AncillaryVariableMetadata.equal, BaseMetadata.__eq__, @@ -1096,3 +1097,7 @@ def values(self): CubeMetadata.__eq__, CubeMetadata.equal, ) + + +#: Convenience collection of lenient metadata services. +SERVICES = SERVICES_COMBINE + SERVICES_DIFFERENCE + SERVICES_EQUAL From c2db01f9c3ecf31455e86ccd2ff8a2642a15de92 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 12 Jun 2020 22:37:33 +0100 Subject: [PATCH 09/45] add lenient test coverage --- lib/iris/aux_factory.py | 6 +- lib/iris/common/lenient.py | 259 +++++- .../tests/unit/common/lenient/__init__.py | 6 + .../tests/unit/common/lenient/test_Lenient.py | 823 ++++++++++++++++++ .../common/lenient/test_lenient_client.py | 182 ++++ .../common/lenient/test_lenient_service.py | 116 +++ .../unit/common/lenient/test_qualname.py | 66 ++ 7 files changed, 1409 insertions(+), 49 deletions(-) create mode 100644 lib/iris/tests/unit/common/lenient/__init__.py create mode 100644 lib/iris/tests/unit/common/lenient/test_Lenient.py create mode 100644 lib/iris/tests/unit/common/lenient/test_lenient_client.py create mode 100644 lib/iris/tests/unit/common/lenient/test_lenient_service.py create mode 100644 lib/iris/tests/unit/common/lenient/test_qualname.py diff --git a/lib/iris/aux_factory.py b/lib/iris/aux_factory.py index d5ccdc28d3..0cc6bf068f 100644 --- a/lib/iris/aux_factory.py +++ b/lib/iris/aux_factory.py @@ -14,7 +14,11 @@ import dask.array as da import numpy as np -from iris.common import CFVariableMixin, CoordMetadata, metadata_manager_factory +from iris.common import ( + CFVariableMixin, + CoordMetadata, + metadata_manager_factory, +) import iris.coords diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 14013aadf6..d4ed0c99b0 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -11,14 +11,21 @@ import threading +# TODO: make qualname private __all__ = [ + "LENIENT", + "LENIENT_PROTECTED", + "Lenient", "lenient_client", "lenient_service", "qualname", - "LENIENT", ] +#: Protected Lenient internal non-client, non-service controlled keys. +LENIENT_PROTECTED = ("active", "enable") + + def lenient_client(*dargs, services=None): """ Decorator that allows a client function/method to declare at runtime that @@ -65,14 +72,20 @@ def func(): ndargs = len(dargs) if ndargs: - assert ndargs == 1, f"Invalid lenient client arguments, expecting 1 got {ndargs}." - assert callable(dargs[0]), f"Invalid lenient client argument, expecting callable." + assert ( + ndargs == 1 + ), f"Invalid lenient client arguments, expecting 1 got {ndargs}." + assert callable( + dargs[0] + ), "Invalid lenient client argument, expecting a callable." - assert not(ndargs and services), f"Invalid lenient client arguments." + assert not ( + ndargs and services + ), "Invalid lenient client, got both arguments and keyword arguments." if ndargs: # The decorator has been used as a simple naked decorator. - func = dargs[0] + (func,) = dargs @wraps(func) def lenient_inner(*args, **kwargs): @@ -95,7 +108,6 @@ def lenient_inner(*args, **kwargs): services = (services,) def lenient_outer(func): - @wraps(func) def lenient_inner(*args, **kwargs): """ @@ -114,34 +126,82 @@ def lenient_inner(*args, **kwargs): return result -def lenient_service(func): +def lenient_service(*dargs): """ Decorator that allows a function/method to declare that it supports lenient - behaviour. + behaviour as a service. Registration is at Python interpreter parse time. + The decorator supports being called with no arguments e.g., + + @lenient_service() + def func(): + pass + + This is equivalent to using it as a simple naked decorator e.g., + + @lenient_service + def func(): + pass + Args: - * func (callable): - Callable function/method to be wrapped by the decorator. + * dargs (tuple of callable): + A tuple containing the callable lenient service function/method to be + wrapped by the decorator. This is automatically populated by Python + through the decorator interface. No argument requires to be manually + provided. Returns: Closure wrapped function/method. """ - LENIENT.register(func) + ndargs = len(dargs) - @wraps(func) - def register_inner(*args, **kwargs): - """ - Closure wrapper function to execute the lenient service - function/method. + if ndargs: + assert ( + ndargs == 1 + ), f"Invalid lenient service arguments, expecting 1 got {ndargs}." + assert callable( + dargs[0] + ), "Invalid lenient service argument, expecting a callable." - """ - return func(*args, **kwargs) + if ndargs: + # The decorator has been used as a simple naked decorator. + (func,) = dargs - return register_inner + LENIENT.register_service(func) + + @wraps(func) + def register_inner(*args, **kwargs): + """ + Closure wrapper function to execute the lenient service + function/method. + + """ + return func(*args, **kwargs) + + result = register_inner + else: + # The decorator has been called with no arguments. + def lenient_outer(func): + LENIENT.register_service(func) + + @wraps(func) + def lenient_inner(*args, **kwargs): + """ + Closure wrapper function to execute the lenient service + function/method. + + """ + return func(*args, **kwargs) + + return lenient_inner + + result = lenient_outer + + return result def qualname(func): @@ -168,26 +228,40 @@ def qualname(func): class Lenient(threading.local): - def __init__(self): + def __init__(self, *args, **kwargs): """ - A container for managing the run-time lenient options for - pre-defined Iris functions and/or methods. + A container for managing the run-time lenient services and client + options for pre-defined functions/methods. - To adjust the values simply update the relevant attribute. - For example:: + Args: - iris.LENIENT.example_lenient_flag = False + * args (callable or str or iterable of callable/str) + A function/method or fully qualified string name of the function/method + acting as a lenient service. - Or, equivalently:: + Kwargs: - iris.LENIENT["example_lenient_flag"] = False + * kwargs (dict of callable/str or iterable of callable/str) + Mapping of lenient client function/method, or fully qualified sting name + of the function/method, to one or more lenient service + function/methods or fully qualified string name of function/methods. + + For example:: + + Lenient(service1, service2, client1=service1, client2=(service1, service2)) Note that, the values of these options are thread-specific. """ - # Currently executing lenient client at runtime. + # The executing lenient client at runtime. self.__dict__["active"] = None + for service in args: + self.register_service(service) + + for client, services in kwargs.items(): + self.register_client(client, services) + def __call__(self, func): """ Determine whether it is valid for the function/method to provide a @@ -218,7 +292,6 @@ def __call__(self, func): def __contains__(self, name): return name in self.__dict__ - # TODO: Confirm whether this should be part of the API. def __getattr__(self, name): if name not in self.__dict__: cls = self.__class__.__name__ @@ -226,8 +299,8 @@ def __getattr__(self, name): raise AttributeError(emsg) return self.__dict__[name] - # TODO: Confirm whether this should be part of the API. def __getitem__(self, name): + name = qualname(name) if name not in self.__dict__: cls = self.__class__.__name__ emsg = f"Invalid {cls!r} option, got {name!r}." @@ -245,17 +318,31 @@ def __repr__(self): return "{}({})".format(cls, joiner.join(kwargs)) def __setattr__(self, name, value): - if name not in self.__dict__: - cls = self.__class__.__name__ - emsg = f"Invalid {cls!r} option, got {name!r}." - raise AttributeError(emsg) - self.__dict__[name] = value + self._common_setter(name, value, AttributeError) def __setitem__(self, name, value): + name = qualname(name) + self._common_setter(name, value, KeyError) + + def _common_setter(self, name, value, exception): + cls = self.__class__.__name__ + if name not in self.__dict__: - cls = self.__class__.__name__ emsg = f"Invalid {cls!r} option, got {name!r}." - raise KeyError(emsg) + raise exception(emsg) + + if name == "active": + value = qualname(value) + if not isinstance(value, str) and value is not None: + emsg = f"Invalid {cls!r} option {name!r}, got {value!r}." + raise ValueError(emsg) + else: + if isinstance(value, str) or callable(value): + value = (value,) + + if isinstance(value, Iterable): + value = tuple([qualname(item) for item in value]) + self.__dict__[name] = value @contextmanager @@ -278,7 +365,7 @@ def context(self, *args, **kwargs): """ # Save the original state. - current_state = self.__dict__.copy() + original_state = self.__dict__.copy() # Temporarily update the state with the kwargs first. for name, value in kwargs.items(): setattr(self, name, value) @@ -294,24 +381,91 @@ def context(self, *args, **kwargs): finally: # Restore the original state. self.__dict__.clear() - self.__dict__.update(current_state) + self.__dict__.update(original_state) - def register(self, func): + def register_client(self, func, services): """ - Register the provided function/method as providing a lenient service. + Add the provided mapping of lenient client function/method to + required lenient service function/methods. Args: * func (callable or str): - A function/method or fully qualified string name of the function/method. + A client function/method or fully qualified string name of the + client function/method. + + * services (callable or str or iterable of callable/str): + One or more service function/methods or fully qualified string names + of the required service function/method. """ func = qualname(func) + cls = self.__class__.__name__ + + if func in LENIENT_PROTECTED: + emsg = ( + f"Cannot register {cls!r} protected non-client, got {func!r}." + ) + raise ValueError(emsg) + if isinstance(services, str) or not isinstance(services, Iterable): + services = (services,) + if not len(services): + emsg = f"Require at least one {cls!r} lenient client service." + raise ValueError(emsg) + services = tuple([qualname(service) for service in services]) + self.__dict__[func] = services + + def register_service(self, func): + """ + Add the provided function/method as providing a lenient service and + activate it. + + Args: + + * func (callable or str): + A service function/method or fully qualified string name of the + service function/method. + + """ + func = qualname(func) + if func in LENIENT_PROTECTED: + cls = self.__class__.__name__ + emsg = ( + f"Cannot register {cls!r} protected non-service, got {func!r}." + ) + raise ValueError(emsg) self.__dict__[func] = True - def unregister(self, func): + def unregister_client(self, func): + """ + Remove the provided function/method as a lenient client using lenient services. + + Args: + + * func (callable or str): + A function/method of fully qualified string name of the function/method. + + """ + func = qualname(func) + cls = self.__class__.__name__ + + if func in LENIENT_PROTECTED: + emsg = f"Cannot unregister {cls!r} protected non-client, got {func!r}." + raise ValueError(emsg) + + if func in self.__dict__: + value = self.__dict__[func] + if isinstance(value, bool): + emsg = f"Cannot unregister {cls!r} non-client, got {func!r}." + raise ValueError(emsg) + del self.__dict__[func] + else: + emsg = f"Cannot unregister unknown {cls!r} client, got {func!r}." + raise ValueError(emsg) + + def unregister_service(self, func): """ - Unregister the provided function/method as providing a lenient service. + Remove the provided function/method as providing a lenient service. Args: @@ -320,13 +474,22 @@ def unregister(self, func): """ func = qualname(func) + cls = self.__class__.__name__ + + if func in LENIENT_PROTECTED: + emsg = f"Cannot unregister {cls!r} protected non-service, got {func!r}." + raise ValueError(emsg) + if func in self.__dict__: - self.__dict__[func] = False + value = self.__dict__[func] + if not isinstance(value, bool): + emsg = f"Cannot unregister {cls!r} non-service, got {func!r}." + raise ValueError(emsg) + del self.__dict__[func] else: - cls = self.__class__.__name__ - emsg = f"Cannot unregister invalid {cls!r} service, got {func!r}." + emsg = f"Cannot unregister unknown {cls!r} service, got {func!r}." raise ValueError(emsg) -#: Instance that manages all Iris run-time lenient options. +#: Instance that manages all Iris run-time lenient client and service options. LENIENT = Lenient() diff --git a/lib/iris/tests/unit/common/lenient/__init__.py b/lib/iris/tests/unit/common/lenient/__init__.py new file mode 100644 index 0000000000..2a99e7a4c2 --- /dev/null +++ b/lib/iris/tests/unit/common/lenient/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris.common.lenient` package.""" diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py new file mode 100644 index 0000000000..62a04b466f --- /dev/null +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -0,0 +1,823 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Unit tests for the :class:`iris.common.lenient.Lenient`. + +""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests + +from collections import Iterable + +from iris.common.lenient import LENIENT_PROTECTED, Lenient, qualname + + +class Test___init__(tests.IrisTest): + def test_default(self): + lenient = Lenient() + expected = dict(active=None) + self.assertEqual(expected, lenient.__dict__) + + def test_args_service_str(self): + service = "service1" + lenient = Lenient(service) + expected = dict(active=None, service1=True) + self.assertEqual(expected, lenient.__dict__) + + def test_args_services_str(self): + services = ("service1", "service2") + lenient = Lenient(*services) + expected = dict(active=None, service1=True, service2=True) + self.assertEqual(expected, lenient.__dict__) + + def test_args_services_callable(self): + def service1(): + pass + + def service2(): + pass + + services = (service1, service2) + lenient = Lenient(*services) + expected = { + "active": None, + qualname(service1): True, + qualname(service2): True, + } + self.assertEqual(expected, lenient.__dict__) + + def test_kwargs_client_str(self): + client = dict(client1="service1") + lenient = Lenient(**client) + expected = dict(active=None, client1=("service1",)) + self.assertEqual(expected, lenient.__dict__) + + def test_kwargs_clients_str(self): + clients = dict(client1="service1", client2="service2") + lenient = Lenient(**clients) + expected = dict( + active=None, client1=("service1",), client2=("service2",) + ) + self.assertEqual(expected, lenient.__dict__) + + def test_kwargs_clients_callable(self): + def client1(): + pass + + def client2(): + pass + + def service1(): + pass + + def service2(): + pass + + qualname_client1 = qualname(client1) + qualname_client2 = qualname(client2) + clients = { + qualname_client1: service1, + qualname_client2: (service1, service2), + } + lenient = Lenient(**clients) + expected = { + "active": None, + qualname(client1): (qualname(service1),), + qualname(client2): (qualname(service1), qualname(service2)), + } + self.assertEqual(expected, lenient.__dict__) + + +class Test___call__(tests.IrisTest): + def setUp(self): + self.client = "myclient" + self.lenient = Lenient() + + def test_missing_service_str(self): + self.assertFalse(self.lenient("myservice")) + + def test_missing_service_callable(self): + def myservice(): + pass + + self.assertFalse(self.lenient(myservice)) + + def test_disabled_service_str(self): + service = "myservice" + self.lenient.__dict__[service] = False + self.assertFalse(self.lenient(service)) + + def test_disable_service_callable(self): + def myservice(): + pass + + qualname_service = qualname(myservice) + self.lenient.__dict__[qualname_service] = False + self.assertFalse(self.lenient(myservice)) + + def test_service_str_with_no_active_client(self): + service = "myservice" + self.lenient.__dict__[service] = True + self.assertFalse(self.lenient(service)) + + def test_service_callable_with_no_active_client(self): + def myservice(): + pass + + qualname_service = qualname(myservice) + self.lenient.__dict__[qualname_service] = True + self.assertFalse(self.lenient(myservice)) + + def test_service_str_with_active_client_with_no_registered_services(self): + service = "myservice" + self.lenient.__dict__[service] = True + self.lenient.__dict__["active"] = self.client + self.assertFalse(self.lenient(service)) + + def test_service_callable_with_active_client_with_no_registered_services( + self, + ): + def myservice(): + pass + + def myclient(): + pass + + qualname_service = qualname(myservice) + self.lenient.__dict__[qualname_service] = True + self.lenient.__dict__["active"] = qualname(myclient) + self.assertFalse(self.lenient(myservice)) + + def test_service_str_with_active_client_with_unmatched_registered_services( + self, + ): + service = "myservice" + self.lenient.__dict__[service] = True + self.lenient.__dict__["active"] = self.client + self.lenient.__dict__[self.client] = ("service1", "service2") + self.assertFalse(self.lenient(service)) + + def test_service_callable_with_active_client_with_unmatched_registered_services( + self, + ): + def myservice(): + pass + + def myclient(): + pass + + qualname_service = qualname(myservice) + qualname_client = qualname(myclient) + self.lenient.__dict__[qualname_service] = True + self.lenient.__dict__["active"] = qualname_client + self.lenient.__dict__[qualname_client] = ("service1", "service2") + self.assertFalse(self.lenient(myservice)) + + def test_service_str_with_active_client_with_registered_services(self): + service = "myservice" + self.lenient.__dict__[service] = True + self.lenient.__dict__["active"] = self.client + self.lenient.__dict__[self.client] = ("service1", "service2", service) + self.assertTrue(self.lenient(service)) + + def test_service_callable_with_active_client_with_registered_services( + self, + ): + def myservice(): + pass + + def myclient(): + pass + + qualname_service = qualname(myservice) + qualname_client = qualname(myclient) + self.lenient.__dict__[qualname_service] = True + self.lenient.__dict__["active"] = qualname_client + self.lenient.__dict__[qualname_client] = ( + "service1", + "service2", + qualname_service, + ) + self.assertTrue(self.lenient(myservice)) + + def test_service_str_with_active_client_with_unmatched_registered_service_str( + self, + ): + service = "myservice" + self.lenient.__dict__[service] = True + self.lenient.__dict__["active"] = self.client + self.lenient.__dict__[self.client] = "serviceXXX" + self.assertFalse(self.lenient(service)) + + def test_service_callable_with_active_client_with_unmatched_registered_service_str( + self, + ): + def myservice(): + pass + + def myclient(): + pass + + qualname_service = qualname(myservice) + qualname_client = qualname(myclient) + self.lenient.__dict__[qualname_service] = True + self.lenient.__dict__["active"] = qualname_client + self.lenient.__dict__[qualname_client] = f"{qualname_service}XXX" + self.assertFalse(self.lenient(myservice)) + + def test_service_str_with_active_client_with_registered_service_str(self): + service = "myservice" + self.lenient.__dict__[service] = True + self.lenient.__dict__["active"] = self.client + self.lenient.__dict__[self.client] = service + self.assertTrue(self.lenient(service)) + + def test_service_callable_with_active_client_with_registered_service_str( + self, + ): + def myservice(): + pass + + def myclient(): + pass + + qualname_service = qualname(myservice) + qualname_client = qualname(myclient) + self.lenient.__dict__[qualname_service] = True + self.lenient.__dict__["active"] = qualname_client + self.lenient.__dict__[qualname_client] = qualname_service + self.assertTrue(self.lenient(myservice)) + + +class Test___contains__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_in(self): + self.assertIn("active", self.lenient) + + def test_not_in(self): + self.assertNotIn("ACTIVATE", self.lenient) + + +class Test___getattr__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_in(self): + self.assertIsNone(self.lenient.active) + + def test_not_in(self): + emsg = "Invalid .* option, got 'wibble'." + with self.assertRaisesRegex(AttributeError, emsg): + _ = self.lenient.wibble + + +class Test__getitem__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_in(self): + self.assertIsNone(self.lenient["active"]) + + def test_in_callable(self): + def service(): + pass + + qualname_service = qualname(service) + self.lenient.__dict__[qualname_service] = True + self.assertTrue(self.lenient[service]) + + def test_not_in(self): + emsg = "Invalid .* option, got 'wibble'." + with self.assertRaisesRegex(KeyError, emsg): + _ = self.lenient["wibble"] + + def test_not_in_callable(self): + def service(): + pass + + qualname_service = qualname(service) + emsg = f"Invalid .* option, got '{qualname_service}'." + with self.assertRaisesRegex(KeyError, emsg): + _ = self.lenient[service] + + +class Test___setattr__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_not_in(self): + emsg = "Invalid .* option, got 'wibble'." + with self.assertRaisesRegex(AttributeError, emsg): + self.lenient.wibble = None + + def test_in_value_str(self): + client = "client" + service = "service" + self.lenient.__dict__[client] = None + self.lenient.client = service + self.assertEqual(self.lenient.__dict__[client], (service,)) + + def test_in_value_callable(self): + def service(): + pass + + client = "client" + qualname_service = qualname(service) + self.lenient.__dict__[client] = None + self.lenient.client = service + self.assertEqual(self.lenient.__dict__[client], (qualname_service,)) + + def test_in_value_bool(self): + client = "client" + self.lenient.__dict__[client] = None + self.lenient.client = True + self.assertTrue(self.lenient.__dict__[client]) + self.assertFalse(isinstance(self.lenient.__dict__[client], Iterable)) + + def test_in_value_iterable(self): + client = "client" + services = ("service1", "service2") + self.lenient.__dict__[client] = None + self.lenient.client = services + self.assertEqual(self.lenient.__dict__[client], services) + + def test_in_value_iterable_callable(self): + def service1(): + pass + + def service2(): + pass + + client = "client" + self.lenient.__dict__[client] = None + qualname_services = (qualname(service1), qualname(service2)) + self.lenient.client = (service1, service2) + self.assertEqual(self.lenient.__dict__[client], qualname_services) + + def test_active_iterable(self): + self.assertIsNone(self.lenient.__dict__["active"]) + emsg = "Invalid .* option 'active'" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.active = (None,) + + def test_active_str(self): + active = "active" + client = "client1" + self.assertIsNone(self.lenient.__dict__[active]) + self.lenient.active = client + self.assertEqual(self.lenient.__dict__[active], client) + + def test_active_callable(self): + def client(): + pass + + active = "active" + qualname_client = qualname(client) + self.assertIsNone(self.lenient.__dict__[active]) + self.lenient.active = client + self.assertEqual(self.lenient.__dict__[active], qualname_client) + + +class Test___setitem__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_not_in(self): + emsg = "Invalid .* option, got 'wibble'." + with self.assertRaisesRegex(KeyError, emsg): + self.lenient["wibble"] = None + + def test_in_value_str(self): + client = "client" + service = "service" + self.lenient.__dict__[client] = None + self.lenient[client] = service + self.assertEqual(self.lenient.__dict__[client], (service,)) + + def test_callable_in_value_str(self): + def client(): + pass + + service = "service" + qualname_client = qualname(client) + self.lenient.__dict__[qualname_client] = None + self.lenient[client] = service + self.assertEqual(self.lenient.__dict__[qualname_client], (service,)) + + def test_in_value_callable(self): + def service(): + pass + + client = "client" + qualname_service = qualname(service) + self.lenient.__dict__[client] = None + self.lenient[client] = service + self.assertEqual(self.lenient.__dict__[client], (qualname_service,)) + + def test_callable_in_value_callable(self): + def client(): + pass + + def service(): + pass + + qualname_client = qualname(client) + qualname_service = qualname(service) + self.lenient.__dict__[qualname_client] = None + self.lenient[client] = service + self.assertEqual( + self.lenient.__dict__[qualname_client], (qualname_service,) + ) + + def test_in_value_bool(self): + client = "client" + self.lenient.__dict__[client] = None + self.lenient[client] = True + self.assertTrue(self.lenient.__dict__[client]) + self.assertFalse(isinstance(self.lenient.__dict__[client], Iterable)) + + def test_callable_in_value_bool(self): + def client(): + pass + + qualname_client = qualname(client) + self.lenient.__dict__[qualname_client] = None + self.lenient[client] = True + self.assertTrue(self.lenient.__dict__[qualname_client]) + self.assertFalse( + isinstance(self.lenient.__dict__[qualname_client], Iterable) + ) + + def test_in_value_iterable(self): + client = "client" + services = ("service1", "service2") + self.lenient.__dict__[client] = None + self.lenient[client] = services + self.assertEqual(self.lenient.__dict__[client], services) + + def test_callable_in_value_iterable(self): + def client(): + pass + + qualname_client = qualname(client) + services = ("service1", "service2") + self.lenient.__dict__[qualname_client] = None + self.lenient[client] = services + self.assertEqual(self.lenient.__dict__[qualname_client], services) + + def test_in_value_iterable_callable(self): + def service1(): + pass + + def service2(): + pass + + client = "client" + self.lenient.__dict__[client] = None + qualname_services = (qualname(service1), qualname(service2)) + self.lenient[client] = (service1, service2) + self.assertEqual(self.lenient.__dict__[client], qualname_services) + + def test_callable_in_value_iterable_callable(self): + def client(): + pass + + def service1(): + pass + + def service2(): + pass + + qualname_client = qualname(client) + self.lenient.__dict__[qualname_client] = None + qualname_services = (qualname(service1), qualname(service2)) + self.lenient[client] = (service1, service2) + self.assertEqual( + self.lenient.__dict__[qualname_client], qualname_services + ) + + def test_active_iterable(self): + active = "active" + self.assertIsNone(self.lenient.__dict__[active]) + emsg = "Invalid .* option 'active'" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient[active] = (None,) + + def test_active_str(self): + active = "active" + client = "client1" + self.assertIsNone(self.lenient.__dict__[active]) + self.lenient[active] = client + self.assertEqual(self.lenient.__dict__[active], client) + + def test_active_callable(self): + def client(): + pass + + active = "active" + qualname_client = qualname(client) + self.assertIsNone(self.lenient.__dict__[active]) + self.lenient[active] = client + self.assertEqual(self.lenient.__dict__[active], qualname_client) + + +class Test_context(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + self.default = dict(active=None) + + def copy(self): + return self.lenient.__dict__.copy() + + def test_nop(self): + pre = self.copy() + with self.lenient.context(): + context = self.copy() + post = self.copy() + self.assertEqual(pre, self.default) + self.assertEqual(context, self.default) + self.assertEqual(post, self.default) + + def test_active_str(self): + client = "client" + pre = self.copy() + with self.lenient.context(active=client): + context = self.copy() + post = self.copy() + self.assertEqual(pre, self.default) + self.assertEqual(context, dict(active=client)) + self.assertEqual(post, self.default) + + def test_active_callable(self): + def client(): + pass + + pre = self.copy() + with self.lenient.context(active=client): + context = self.copy() + post = self.copy() + qualname_client = qualname(client) + self.assertEqual(pre, self.default) + self.assertEqual(context, dict(active=qualname_client)) + self.assertEqual(post, self.default) + + def test_kwargs(self): + client = "client" + self.lenient.__dict__["service1"] = False + self.lenient.__dict__["service2"] = False + pre = self.copy() + with self.lenient.context(active=client, service1=True, service2=True): + context = self.copy() + post = self.copy() + default = dict(active=None, service1=False, service2=False) + self.assertEqual(pre, default) + expected = dict(active=client, service1=True, service2=True) + self.assertEqual(context, expected) + self.assertEqual(post, default) + + def test_args_str(self): + client = "client" + services = ("service1", "service2") + pre = self.copy() + with self.lenient.context(*services, active=client): + context = self.copy() + post = self.copy() + self.assertEqual(pre, self.default) + expected = dict(active=client, client=services) + self.assertEqual(context, expected) + self.assertEqual(post, self.default) + + def test_args_callable(self): + def service1(): + pass + + def service2(): + pass + + client = "client" + services = (service1, service2) + pre = self.copy() + with self.lenient.context(*services, active=client): + context = self.copy() + post = self.copy() + qualname_services = tuple([qualname(service) for service in services]) + self.assertEqual(pre, self.default) + expected = dict(active=client, client=qualname_services) + self.assertEqual(context, expected) + self.assertEqual(post, self.default) + + def test_context_runtime(self): + services = ("service1", "service2") + pre = self.copy() + with self.lenient.context(*services): + context = self.copy() + post = self.copy() + self.assertEqual(pre, self.default) + expected = dict(active="context", context=services) + self.assertEqual(context, expected) + self.assertEqual(post, self.default) + + +class Test_register_client(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_not_protected(self): + emsg = "Cannot register .* protected non-client" + for protected in LENIENT_PROTECTED: + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.register_client(protected, "service") + + def test_str_service_str(self): + client = "client" + services = "service" + self.lenient.register_client(client, services) + self.assertIn(client, self.lenient.__dict__) + self.assertEqual(self.lenient.__dict__[client], (services,)) + + def test_str_services_str(self): + client = "client" + services = ("service1", "service2") + self.lenient.register_client(client, services) + self.assertIn(client, self.lenient.__dict__) + self.assertEqual(self.lenient.__dict__[client], services) + + def test_callable_service_callable(self): + def client(): + pass + + def service(): + pass + + qualname_client = qualname(client) + qualname_service = qualname(service) + self.lenient.register_client(client, service) + self.assertIn(qualname_client, self.lenient.__dict__) + self.assertEqual( + self.lenient.__dict__[qualname_client], (qualname_service,) + ) + + def test_callable_services_callable(self): + def client(): + pass + + def service1(): + pass + + def service2(): + pass + + qualname_client = qualname(client) + qualname_services = (qualname(service1), qualname(service2)) + self.lenient.register_client(client, (service1, service2)) + self.assertIn(qualname_client, self.lenient.__dict__) + self.assertEqual( + self.lenient.__dict__[qualname_client], qualname_services + ) + + def test_services_empty(self): + emsg = "Require at least one .* lenient client service." + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.register_client("client", ()) + + +class Test_register_service(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_str(self): + service = "service" + self.assertNotIn(service, self.lenient.__dict__) + self.lenient.register_service(service) + self.assertIn(service, self.lenient.__dict__) + self.assertFalse(isinstance(self.lenient.__dict__[service], Iterable)) + self.assertTrue(self.lenient.__dict__[service]) + + def test_callable(self): + def service(): + pass + + qualname_service = qualname(service) + self.assertNotIn(qualname_service, self.lenient.__dict__) + self.lenient.register_service(service) + self.assertIn(qualname_service, self.lenient.__dict__) + self.assertFalse( + isinstance(self.lenient.__dict__[qualname_service], Iterable) + ) + self.assertTrue(self.lenient.__dict__[qualname_service]) + + def test_not_protected(self): + emsg = "Cannot register .* protected non-service" + for protected in LENIENT_PROTECTED: + self.lenient.__dict__[protected] = None + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.register_service("active") + + +class Test_unregister_client(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_not_protected(self): + emsg = "Cannot unregister .* protected non-client" + for protected in LENIENT_PROTECTED: + self.lenient.__dict__[protected] = None + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_client(protected) + + def test_not_in(self): + emsg = "Cannot unregister unknown .* client" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_client("client") + + def test_not_client(self): + client = "client" + self.lenient.__dict__[client] = True + emsg = "Cannot unregister .* non-client" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_client(client) + + def test_not_client_callable(self): + def client(): + pass + + qualname_client = qualname(client) + self.lenient.__dict__[qualname_client] = True + emsg = "Cannot unregister .* non-client" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_client(client) + + def test_str(self): + client = "client" + self.lenient.__dict__[client] = (None,) + self.lenient.unregister_client(client) + self.assertNotIn(client, self.lenient.__dict__) + + def test_callable(self): + def client(): + pass + + qualname_client = qualname(client) + self.lenient.__dict__[qualname_client] = (None,) + self.lenient.unregister_client(client) + self.assertNotIn(qualname_client, self.lenient.__dict__) + + +class Test_unregister_service(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_not_protected(self): + emsg = "Cannot unregister .* protected non-service" + for protected in LENIENT_PROTECTED: + self.lenient.__dict__[protected] = None + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_service(protected) + + def test_not_in(self): + emsg = "Cannot unregister unknown .* service" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_service("service") + + def test_not_service(self): + service = "service" + self.lenient.__dict__[service] = (None,) + emsg = "Cannot unregister .* non-service" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_service(service) + + def test_not_service_callable(self): + def service(): + pass + + qualname_service = qualname(service) + self.lenient.__dict__[qualname_service] = (None,) + emsg = "Cannot unregister .* non-service" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.unregister_service(service) + + def test_str(self): + service = "service" + self.lenient.__dict__[service] = True + self.lenient.unregister_service(service) + self.assertNotIn(service, self.lenient.__dict__) + + def test_callable(self): + def service(): + pass + + qualname_service = qualname(service) + self.lenient.__dict__[qualname_service] = True + self.lenient.unregister_service(service) + self.assertNotIn(qualname_service, self.lenient.__dict__) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test_lenient_client.py b/lib/iris/tests/unit/common/lenient/test_lenient_client.py new file mode 100644 index 0000000000..20d0b47e23 --- /dev/null +++ b/lib/iris/tests/unit/common/lenient/test_lenient_client.py @@ -0,0 +1,182 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Unit tests for the :func:`iris.common.lenient.lenient_client`. + +""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests + +from inspect import getmodule +from unittest.mock import sentinel + +from iris.common.lenient import LENIENT, lenient_client + + +class Test(tests.IrisTest): + def setUp(self): + module_name = getmodule(self).__name__ + self.client = f"{module_name}" + ".Test.{}..myclient" + self.service = f"{module_name}" + ".Test.{}..myservice" + self.active = "active" + self.args_in = sentinel.arg1, sentinel.arg2 + self.kwargs_in = dict(kwarg1=sentinel.kwarg1, kwarg2=sentinel.kwarg2) + + def test_args_too_many(self): + emsg = "Invalid lenient client arguments, expecting 1" + with self.assertRaisesRegex(AssertionError, emsg): + lenient_client(None, None) + + def test_args_not_callable(self): + emsg = "Invalid lenient client argument, expecting a callable" + with self.assertRaisesRegex(AssertionError, emsg): + lenient_client(None) + + def test_args_and_kwargs(self): + def func(): + pass + + emsg = ( + "Invalid lenient client, got both arguments and keyword arguments" + ) + with self.assertRaisesRegex(AssertionError, emsg): + lenient_client(func, services=func) + + def test_call_naked(self): + @lenient_client + def myclient(): + return LENIENT.__dict__.copy() + + result = myclient() + self.assertIn(self.active, result) + qualname_client = self.client.format("test_call_naked") + self.assertEqual(result[self.active], qualname_client) + self.assertNotIn(qualname_client, result) + + def test_call_naked_alternative(self): + def myclient(): + return LENIENT.__dict__.copy() + + result = lenient_client(myclient)() + self.assertIn(self.active, result) + qualname_client = self.client.format("test_call_naked_alternative") + self.assertEqual(result[self.active], qualname_client) + self.assertNotIn(qualname_client, result) + + def test_call_naked_client_args_kwargs(self): + @lenient_client + def myclient(*args, **kwargs): + return args, kwargs + + args_out, kwargs_out = myclient(*self.args_in, **self.kwargs_in) + self.assertEqual(args_out, self.args_in) + self.assertEqual(kwargs_out, self.kwargs_in) + + def test_call_naked_doc(self): + @lenient_client + def myclient(): + """myclient doc-string""" + + self.assertEqual(myclient.__doc__, "myclient doc-string") + + def test_call_no_kwargs(self): + @lenient_client() + def myclient(): + return LENIENT.__dict__.copy() + + result = myclient() + self.assertIn(self.active, result) + qualname_client = self.client.format("test_call_no_kwargs") + self.assertEqual(result[self.active], qualname_client) + self.assertNotIn(qualname_client, result) + + def test_call_no_kwargs_alternative(self): + def myclient(): + return LENIENT.__dict__.copy() + + result = (lenient_client())(myclient)() + self.assertIn(self.active, result) + qualname_client = self.client.format("test_call_no_kwargs_alternative") + self.assertEqual(result[self.active], qualname_client) + self.assertNotIn(qualname_client, result) + + def test_call_kwargs_none(self): + @lenient_client(services=None) + def myclient(): + return LENIENT.__dict__.copy() + + result = myclient() + self.assertIn(self.active, result) + qualname_client = self.client.format("test_call_kwargs_none") + self.assertEqual(result[self.active], qualname_client) + self.assertNotIn(qualname_client, result) + + def test_call_kwargs_single(self): + service = sentinel.service + + @lenient_client(services=service) + def myclient(): + return LENIENT.__dict__.copy() + + result = myclient() + self.assertIn(self.active, result) + qualname_client = self.client.format("test_call_kwargs_single") + self.assertEqual(result[self.active], qualname_client) + self.assertIn(qualname_client, result) + self.assertEqual(result[qualname_client], (service,)) + + def test_call_kwargs_single_callable(self): + def myservice(): + pass + + @lenient_client(services=myservice) + def myclient(): + return LENIENT.__dict__.copy() + + test_name = "test_call_kwargs_single_callable" + result = myclient() + self.assertIn(self.active, result) + qualname_client = self.client.format(test_name) + self.assertEqual(result[self.active], qualname_client) + self.assertIn(qualname_client, result) + qualname_services = (self.service.format(test_name),) + self.assertEqual(result[qualname_client], qualname_services) + + def test_call_kwargs_iterable(self): + services = (sentinel.service1, sentinel.service2) + + @lenient_client(services=services) + def myclient(): + return LENIENT.__dict__.copy() + + result = myclient() + self.assertIn(self.active, result) + qualname_client = self.client.format("test_call_kwargs_iterable") + self.assertEqual(result[self.active], qualname_client) + self.assertIn(qualname_client, result) + self.assertEqual(result[qualname_client], services) + + def test_call_client_args_kwargs(self): + @lenient_client() + def myclient(*args, **kwargs): + return args, kwargs + + args_out, kwargs_out = myclient(*self.args_in, **self.kwargs_in) + self.assertEqual(args_out, self.args_in) + self.assertEqual(kwargs_out, self.kwargs_in) + + def test_call_doc(self): + @lenient_client() + def myclient(): + """myclient doc-string""" + + self.assertEqual(myclient.__doc__, "myclient doc-string") + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test_lenient_service.py b/lib/iris/tests/unit/common/lenient/test_lenient_service.py new file mode 100644 index 0000000000..1124b0d01f --- /dev/null +++ b/lib/iris/tests/unit/common/lenient/test_lenient_service.py @@ -0,0 +1,116 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Unit tests for the :func:`iris.common.lenient.lenient_service`. + +""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests + +from inspect import getmodule +from unittest.mock import sentinel + +from iris.common.lenient import LENIENT, lenient_service + + +class Test(tests.IrisTest): + def setUp(self): + module_name = getmodule(self).__name__ + self.service = f"{module_name}" + ".Test.{}..myservice" + self.args_in = sentinel.arg1, sentinel.arg2 + self.kwargs_in = dict(kwarg1=sentinel.kwarg1, kwarg2=sentinel.kwarg2) + + def test_args_too_many(self): + emsg = "Invalid lenient service arguments, expecting 1" + with self.assertRaisesRegex(AssertionError, emsg): + lenient_service(None, None) + + def test_args_not_callable(self): + emsg = "Invalid lenient service argument, expecting a callable" + with self.assertRaisesRegex(AssertionError, emsg): + lenient_service(None) + + def test_call_naked(self): + @lenient_service + def myservice(): + return LENIENT.__dict__.copy() + + qualname_service = self.service.format("test_call_naked") + state = LENIENT.__dict__ + self.assertIn(qualname_service, state) + self.assertTrue(state[qualname_service]) + result = myservice() + self.assertIn(qualname_service, result) + self.assertTrue(result[qualname_service]) + + def test_call_naked_alternative(self): + def myservice(): + return LENIENT.__dict__.copy() + + qualname_service = self.service.format("test_call_naked_alternative") + result = lenient_service(myservice)() + self.assertIn(qualname_service, result) + self.assertTrue(result[qualname_service]) + + def test_call_naked_service_args_kwargs(self): + @lenient_service + def myservice(*args, **kwargs): + return args, kwargs + + args_out, kwargs_out = myservice(*self.args_in, **self.kwargs_in) + self.assertEqual(args_out, self.args_in) + self.assertEqual(kwargs_out, self.kwargs_in) + + def test_call_naked_doc(self): + @lenient_service + def myservice(): + """myservice doc-string""" + + self.assertEqual(myservice.__doc__, "myservice doc-string") + + def test_call(self): + @lenient_service() + def myservice(): + return LENIENT.__dict__.copy() + + qualname_service = self.service.format("test_call") + state = LENIENT.__dict__ + self.assertIn(qualname_service, state) + self.assertTrue(state[qualname_service]) + result = myservice() + self.assertIn(qualname_service, result) + self.assertTrue(result[qualname_service]) + + def test_call_alternative(self): + def myservice(): + return LENIENT.__dict__.copy() + + qualname_service = self.service.format("test_call_alternative") + result = (lenient_service())(myservice)() + self.assertIn(qualname_service, result) + self.assertTrue(result[qualname_service]) + + def test_call_service_args_kwargs(self): + @lenient_service() + def myservice(*args, **kwargs): + return args, kwargs + + args_out, kwargs_out = myservice(*self.args_in, **self.kwargs_in) + self.assertEqual(args_out, self.args_in) + self.assertEqual(kwargs_out, self.kwargs_in) + + def test_call_doc(self): + @lenient_service() + def myservice(): + """myservice doc-string""" + + self.assertEqual(myservice.__doc__, "myservice doc-string") + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test_qualname.py b/lib/iris/tests/unit/common/lenient/test_qualname.py new file mode 100644 index 0000000000..a5622873d0 --- /dev/null +++ b/lib/iris/tests/unit/common/lenient/test_qualname.py @@ -0,0 +1,66 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Unit tests for the :func:`iris.common.lenient.qualname`. + +""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests + +from inspect import getmodule +from unittest.mock import sentinel + +from iris.common.lenient import qualname + + +class Test(tests.IrisTest): + def setUp(self): + module_name = getmodule(self).__name__ + self.locals = f"{module_name}" + ".Test.{}..{}" + + def test_pass_thru_non_callable(self): + func = sentinel.func + result = qualname(func) + self.assertEqual(result, func) + + def test_callable_function_local(self): + def myfunc(): + pass + + qualname_func = self.locals.format( + "test_callable_function_local", "myfunc" + ) + result = qualname(myfunc) + self.assertEqual(result, qualname_func) + + def test_callable_function(self): + import iris + + result = qualname(iris.load) + self.assertEqual(result, "iris.load") + + def test_callable_method_local(self): + class MyClass: + def mymethod(self): + pass + + qualname_method = self.locals.format( + "test_callable_method_local", "MyClass.mymethod" + ) + result = qualname(MyClass.mymethod) + self.assertEqual(result, qualname_method) + + def test_callable_method(self): + import iris + + result = qualname(iris.cube.Cube.add_ancillary_variable) + self.assertEqual(result, "iris.cube.Cube.add_ancillary_variable") + + +if __name__ == "__main__": + tests.main() From 066240049aed714a09655ce32aff9d5b8948e614 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 12 Jun 2020 22:53:30 +0100 Subject: [PATCH 10/45] purge qualname usage in metadata.py --- lib/iris/common/lenient.py | 1 - lib/iris/common/metadata.py | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index d4ed0c99b0..a3a6ed8a48 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -11,7 +11,6 @@ import threading -# TODO: make qualname private __all__ = [ "LENIENT", "LENIENT_PROTECTED", diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index c60091b47e..9fd2f68570 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -10,7 +10,7 @@ from functools import wraps import re -from .lenient import LENIENT, lenient_service, qualname +from .lenient import LENIENT, lenient_service __all__ = [ @@ -380,9 +380,9 @@ def combine(self, other, lenient=None): values = self._combine(other) else: if lenient: - args, kwargs = (qualname(self.combine),), dict() + args, kwargs = (self.combine,), dict() else: - args, kwargs = (), {qualname(self.combine): False} + args, kwargs = (), {self.combine: False} with LENIENT.context(*args, **kwargs): values = self._combine(other) @@ -425,9 +425,9 @@ def difference(self, other, lenient=None): values = self._difference(other) else: if lenient: - args, kwargs = (qualname(self.difference),), dict() + args, kwargs = (self.difference,), dict() else: - args, kwargs = (), {qualname(self.difference): False} + args, kwargs = (), {self.difference: False} with LENIENT.context(*args, **kwargs): values = self._difference(other) @@ -458,9 +458,9 @@ def equal(self, other, lenient=None): result = self.__eq__(other) else: if lenient: - args, kwargs = (qualname(self.equal),), dict() + args, kwargs = (self.equal,), dict() else: - args, kwargs = (), {qualname(self.equal): False} + args, kwargs = (), {self.equal: False} with LENIENT.context(*args, **kwargs): result = self.__eq__(other) From ff5166e48047c009e0dec6a8a56c5f966e4ad7a9 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Sat, 13 Jun 2020 00:06:22 +0100 Subject: [PATCH 11/45] support global enable for lenient services --- lib/iris/common/lenient.py | 31 +++-- .../tests/unit/common/lenient/test_Lenient.py | 117 +++++++++++++----- 2 files changed, 104 insertions(+), 44 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index a3a6ed8a48..197e51f15b 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -13,6 +13,7 @@ __all__ = [ "LENIENT", + "LENIENT_ENABLE", "LENIENT_PROTECTED", "Lenient", "lenient_client", @@ -21,6 +22,9 @@ ] +#: Default Lenient services global activation state. +LENIENT_ENABLE = True + #: Protected Lenient internal non-client, non-service controlled keys. LENIENT_PROTECTED = ("active", "enable") @@ -254,6 +258,8 @@ def __init__(self, *args, **kwargs): """ # The executing lenient client at runtime. self.__dict__["active"] = None + # The global lenient services state activation switch. + self.__dict__["enable"] = LENIENT_ENABLE for service in args: self.register_service(service) @@ -276,16 +282,17 @@ def __call__(self, func): """ result = False - service = qualname(func) - if service in self and self.__dict__[service]: - active = self.__dict__["active"] - if active is not None and active in self: - services = self.__dict__[active] - if isinstance(services, str) or not isinstance( - services, Iterable - ): - services = (services,) - result = service in services + if self.__dict__["enable"]: + service = qualname(func) + if service in self and self.__dict__[service]: + active = self.__dict__["active"] + if active is not None and active in self: + services = self.__dict__[active] + if isinstance(services, str) or not isinstance( + services, Iterable + ): + services = (services,) + result = service in services return result def __contains__(self, name): @@ -335,6 +342,10 @@ def _common_setter(self, name, value, exception): if not isinstance(value, str) and value is not None: emsg = f"Invalid {cls!r} option {name!r}, got {value!r}." raise ValueError(emsg) + elif name == "enable": + if not isinstance(value, bool): + emsg = f"Invalid {cls!r} option {name!r}, got {value!r}." + raise ValueError(emsg) else: if isinstance(value, str) or callable(value): value = (value,) diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index 62a04b466f..cdd58da34d 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -14,26 +14,33 @@ from collections import Iterable -from iris.common.lenient import LENIENT_PROTECTED, Lenient, qualname +from iris.common.lenient import ( + LENIENT_ENABLE, + LENIENT_PROTECTED, + Lenient, + qualname, +) class Test___init__(tests.IrisTest): + def setUp(self): + self.expected = dict(active=None, enable=LENIENT_ENABLE) + def test_default(self): lenient = Lenient() - expected = dict(active=None) - self.assertEqual(expected, lenient.__dict__) + self.assertEqual(self.expected, lenient.__dict__) def test_args_service_str(self): service = "service1" lenient = Lenient(service) - expected = dict(active=None, service1=True) - self.assertEqual(expected, lenient.__dict__) + self.expected.update(dict(service1=True)) + self.assertEqual(self.expected, lenient.__dict__) def test_args_services_str(self): services = ("service1", "service2") lenient = Lenient(*services) - expected = dict(active=None, service1=True, service2=True) - self.assertEqual(expected, lenient.__dict__) + self.expected.update(dict(service1=True, service2=True)) + self.assertEqual(self.expected, lenient.__dict__) def test_args_services_callable(self): def service1(): @@ -44,26 +51,24 @@ def service2(): services = (service1, service2) lenient = Lenient(*services) - expected = { - "active": None, - qualname(service1): True, - qualname(service2): True, - } - self.assertEqual(expected, lenient.__dict__) + self.expected.update( + {qualname(service1): True, qualname(service2): True,} + ) + self.assertEqual(self.expected, lenient.__dict__) def test_kwargs_client_str(self): client = dict(client1="service1") lenient = Lenient(**client) - expected = dict(active=None, client1=("service1",)) - self.assertEqual(expected, lenient.__dict__) + self.expected.update(dict(client1=("service1",))) + self.assertEqual(self.expected, lenient.__dict__) def test_kwargs_clients_str(self): clients = dict(client1="service1", client2="service2") lenient = Lenient(**clients) - expected = dict( - active=None, client1=("service1",), client2=("service2",) + self.expected.update( + dict(client1=("service1",), client2=("service2",)) ) - self.assertEqual(expected, lenient.__dict__) + self.assertEqual(self.expected, lenient.__dict__) def test_kwargs_clients_callable(self): def client1(): @@ -85,12 +90,13 @@ def service2(): qualname_client2: (service1, service2), } lenient = Lenient(**clients) - expected = { - "active": None, - qualname(client1): (qualname(service1),), - qualname(client2): (qualname(service1), qualname(service2)), - } - self.assertEqual(expected, lenient.__dict__) + self.expected.update( + { + qualname(client1): (qualname(service1),), + qualname(client2): (qualname(service1), qualname(service2)), + } + ) + self.assertEqual(self.expected, lenient.__dict__) class Test___call__(tests.IrisTest): @@ -253,6 +259,15 @@ def myclient(): self.lenient.__dict__[qualname_client] = qualname_service self.assertTrue(self.lenient(myservice)) + def test_enable(self): + service = "myservice" + self.lenient.__dict__[service] = True + self.lenient.__dict__["active"] = self.client + self.lenient.__dict__[self.client] = service + self.assertTrue(self.lenient(service)) + self.lenient.__dict__["enable"] = False + self.assertFalse(self.lenient(service)) + class Test___contains__(tests.IrisTest): def setUp(self): @@ -384,6 +399,19 @@ def client(): self.lenient.active = client self.assertEqual(self.lenient.__dict__[active], qualname_client) + def test_enable(self): + enable = "enable" + self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE) + self.lenient.enable = True + self.assertTrue(self.lenient.__dict__[enable]) + self.lenient.enable = False + self.assertFalse(self.lenient.__dict__[enable]) + + def test_enable_invalid(self): + emsg = "Invalid .* option 'enable'" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.enable = None + class Test___setitem__(tests.IrisTest): def setUp(self): @@ -527,11 +555,24 @@ def client(): self.lenient[active] = client self.assertEqual(self.lenient.__dict__[active], qualname_client) + def test_enable(self): + enable = "enable" + self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE) + self.lenient[enable] = True + self.assertTrue(self.lenient.__dict__[enable]) + self.lenient[enable] = False + self.assertFalse(self.lenient.__dict__[enable]) + + def test_enable_invalid(self): + emsg = "Invalid .* option 'enable'" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient["enable"] = None + class Test_context(tests.IrisTest): def setUp(self): self.lenient = Lenient() - self.default = dict(active=None) + self.default = dict(active=None, enable=LENIENT_ENABLE) def copy(self): return self.lenient.__dict__.copy() @@ -552,7 +593,9 @@ def test_active_str(self): context = self.copy() post = self.copy() self.assertEqual(pre, self.default) - self.assertEqual(context, dict(active=client)) + expected = self.default.copy() + expected.update(dict(active=client)) + self.assertEqual(context, expected) self.assertEqual(post, self.default) def test_active_callable(self): @@ -565,7 +608,9 @@ def client(): post = self.copy() qualname_client = qualname(client) self.assertEqual(pre, self.default) - self.assertEqual(context, dict(active=qualname_client)) + expected = self.default.copy() + expected.update(dict(active=qualname_client)) + self.assertEqual(context, expected) self.assertEqual(post, self.default) def test_kwargs(self): @@ -576,11 +621,12 @@ def test_kwargs(self): with self.lenient.context(active=client, service1=True, service2=True): context = self.copy() post = self.copy() - default = dict(active=None, service1=False, service2=False) - self.assertEqual(pre, default) - expected = dict(active=client, service1=True, service2=True) + self.default.update(dict(service1=False, service2=False)) + self.assertEqual(pre, self.default) + expected = self.default.copy() + expected.update(dict(active=client, service1=True, service2=True)) self.assertEqual(context, expected) - self.assertEqual(post, default) + self.assertEqual(post, self.default) def test_args_str(self): client = "client" @@ -590,7 +636,8 @@ def test_args_str(self): context = self.copy() post = self.copy() self.assertEqual(pre, self.default) - expected = dict(active=client, client=services) + expected = self.default.copy() + expected.update(dict(active=client, client=services)) self.assertEqual(context, expected) self.assertEqual(post, self.default) @@ -609,7 +656,8 @@ def service2(): post = self.copy() qualname_services = tuple([qualname(service) for service in services]) self.assertEqual(pre, self.default) - expected = dict(active=client, client=qualname_services) + expected = self.default.copy() + expected.update(dict(active=client, client=qualname_services)) self.assertEqual(context, expected) self.assertEqual(post, self.default) @@ -620,7 +668,8 @@ def test_context_runtime(self): context = self.copy() post = self.copy() self.assertEqual(pre, self.default) - expected = dict(active="context", context=services) + expected = self.default.copy() + expected.update(dict(active="context", context=services)) self.assertEqual(context, expected) self.assertEqual(post, self.default) From 35a603dbf0e1ffa0eb12ae359b9b5be37e59c76d Mon Sep 17 00:00:00 2001 From: Bill Little Date: Sat, 13 Jun 2020 01:42:17 +0100 Subject: [PATCH 12/45] support partial mapping metadata assignment --- lib/iris/common/metadata.py | 1 + lib/iris/common/mixin.py | 5 +- .../unit/common/mixin/test_CFVariableMixin.py | 86 +++++++++++++++---- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 9fd2f68570..dc4d5de6b5 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -1014,6 +1014,7 @@ def __setstate__(self, state): @property def fields(self): """Return the name of the metadata members.""" + # Proxy for built-in namedtuple._fields property. return self.cls._fields @property diff --git a/lib/iris/common/mixin.py b/lib/iris/common/mixin.py index 8133bb50a4..d68c556fa2 100644 --- a/lib/iris/common/mixin.py +++ b/lib/iris/common/mixin.py @@ -215,9 +215,8 @@ def metadata(self, metadata): metadata = metadata._asdict() if isinstance(metadata, Mapping): - missing = [ - field for field in fields if field not in metadata - ] + missing = False + fields = [field for field in fields if field in metadata] else: # Generic iterable/container with no associated keys. missing = [ diff --git a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py index 334c908e20..5ac9361e4f 100644 --- a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py +++ b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py @@ -17,7 +17,13 @@ from cf_units import Unit -from iris.common.metadata import BaseMetadata +from iris.common.metadata import ( + AncillaryVariableMetadata, + BaseMetadata, + CellMeasureMetadata, + CoordMetadata, + CubeMetadata, +) from iris.common.mixin import CFVariableMixin, LimitedAttributeDict @@ -174,12 +180,15 @@ def test_dict(self): self.item._metadata_manager.attributes, self.attributes ) - def test_dict__missing(self): + def test_dict__partial(self): metadata = dict(**self.args) del metadata["standard_name"] - emsg = "Invalid .* metadata, require 'standard_name' to be specified." - with self.assertRaisesRegex(TypeError, emsg): - self.item.metadata = metadata + self.item.metadata = metadata + metadata["standard_name"] = mock.sentinel.standard_name + self.assertEqual(self.item._metadata_manager.values, metadata) + self.assertIsNot( + self.item._metadata_manager.attributes, self.attributes + ) def test_ordereddict(self): metadata = self.args @@ -189,13 +198,14 @@ def test_ordereddict(self): self.item._metadata_manager.attributes, self.attributes ) - def test_ordereddict__missing(self): + def test_ordereddict__partial(self): metadata = self.args del metadata["long_name"] del metadata["units"] - emsg = "Invalid .* metadata, require 'long_name', 'units' to be specified." - with self.assertRaisesRegex(TypeError, emsg): - self.item.metadata = metadata + self.item.metadata = metadata + metadata["long_name"] = mock.sentinel.long_name + metadata["units"] = mock.sentinel.units + self.assertEqual(self.item._metadata_manager.values, metadata) def test_tuple(self): metadata = tuple(self.args.values()) @@ -232,16 +242,28 @@ def test_namedtuple(self): self.item._metadata_manager.attributes, metadata.attributes ) - def test_namedtuple__missing(self): + def test_namedtuple__partial(self): Metadata = namedtuple( "Metadata", ("standard_name", "long_name", "var_name", "units") ) - metadata = Metadata(standard_name=1, long_name=2, var_name=3, units=4) - emsg = "Invalid .* metadata, require 'attributes' to be specified." - with self.assertRaisesRegex(TypeError, emsg): - self.item.metadata = metadata + del self.args["attributes"] + metadata = Metadata(**self.args) + self.item.metadata = metadata + expected = metadata._asdict() + expected.update(dict(attributes=mock.sentinel.attributes)) + self.assertEqual(self.item._metadata_manager.values, expected) - def test_class(self): + def test_class_ancillaryvariablemetadata(self): + metadata = AncillaryVariableMetadata(**self.args) + self.item.metadata = metadata + self.assertEqual( + self.item._metadata_manager.values, metadata._asdict() + ) + self.assertIsNot( + self.item._metadata_manager.attributes, metadata.attributes + ) + + def test_class_basemetadata(self): metadata = BaseMetadata(**self.args) self.item.metadata = metadata self.assertEqual( @@ -251,6 +273,40 @@ def test_class(self): self.item._metadata_manager.attributes, metadata.attributes ) + def test_class_cellmeasuremetadata(self): + self.args["measure"] = None + metadata = CellMeasureMetadata(**self.args) + self.item.metadata = metadata + expected = metadata._asdict() + del expected["measure"] + self.assertEqual(self.item._metadata_manager.values, expected) + self.assertIsNot( + self.item._metadata_manager.attributes, metadata.attributes + ) + + def test_class_coordmetadata(self): + self.args.update(dict(coord_system=None, climatological=False)) + metadata = CoordMetadata(**self.args) + self.item.metadata = metadata + expected = metadata._asdict() + del expected["coord_system"] + del expected["climatological"] + self.assertEqual(self.item._metadata_manager.values, expected) + self.assertIsNot( + self.item._metadata_manager.attributes, metadata.attributes + ) + + def test_class_cubemetadata(self): + self.args["cell_methods"] = None + metadata = CubeMetadata(**self.args) + self.item.metadata = metadata + expected = metadata._asdict() + del expected["cell_methods"] + self.assertEqual(self.item._metadata_manager.values, expected) + self.assertIsNot( + self.item._metadata_manager.attributes, metadata.attributes + ) + class Test_rename(tests.IrisTest): def setUp(self): From c439579e2ad9a1397aa76ae1c4871369e7aaa6bc Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 15 Jun 2020 12:15:15 +0100 Subject: [PATCH 13/45] purge Lenient.__setattr__ from api --- lib/iris/common/lenient.py | 52 +++++--- lib/iris/common/metadata.py | 23 ++-- .../tests/unit/common/lenient/test_Lenient.py | 118 ++++-------------- 3 files changed, 70 insertions(+), 123 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 197e51f15b..e2d89a5f17 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -13,8 +13,6 @@ __all__ = [ "LENIENT", - "LENIENT_ENABLE", - "LENIENT_PROTECTED", "Lenient", "lenient_client", "lenient_service", @@ -23,9 +21,9 @@ #: Default Lenient services global activation state. -LENIENT_ENABLE = True +LENIENT_ENABLE_DEFAULT = True -#: Protected Lenient internal non-client, non-service controlled keys. +#: Protected Lenient internal non-client, non-service keys. LENIENT_PROTECTED = ("active", "enable") @@ -259,7 +257,7 @@ def __init__(self, *args, **kwargs): # The executing lenient client at runtime. self.__dict__["active"] = None # The global lenient services state activation switch. - self.__dict__["enable"] = LENIENT_ENABLE + self.__dict__["enable"] = LENIENT_ENABLE_DEFAULT for service in args: self.register_service(service) @@ -323,37 +321,28 @@ def __repr__(self): joiner = ",\n{}".format(" " * width) return "{}({})".format(cls, joiner.join(kwargs)) - def __setattr__(self, name, value): - self._common_setter(name, value, AttributeError) - def __setitem__(self, name, value): name = qualname(name) - self._common_setter(name, value, KeyError) - - def _common_setter(self, name, value, exception): cls = self.__class__.__name__ if name not in self.__dict__: emsg = f"Invalid {cls!r} option, got {name!r}." - raise exception(emsg) + raise KeyError(emsg) if name == "active": value = qualname(value) if not isinstance(value, str) and value is not None: emsg = f"Invalid {cls!r} option {name!r}, got {value!r}." raise ValueError(emsg) + self.__dict__[name] = value elif name == "enable": - if not isinstance(value, bool): - emsg = f"Invalid {cls!r} option {name!r}, got {value!r}." - raise ValueError(emsg) + self.enable = value else: if isinstance(value, str) or callable(value): value = (value,) - if isinstance(value, Iterable): value = tuple([qualname(item) for item in value]) - - self.__dict__[name] = value + self.__dict__[name] = value @contextmanager def context(self, *args, **kwargs): @@ -378,7 +367,7 @@ def context(self, *args, **kwargs): original_state = self.__dict__.copy() # Temporarily update the state with the kwargs first. for name, value in kwargs.items(): - setattr(self, name, value) + self[name] = value # Temporarily update the client/services, if provided. if args: active = self.__dict__["active"] @@ -393,6 +382,31 @@ def context(self, *args, **kwargs): self.__dict__.clear() self.__dict__.update(original_state) + @property + def enable(self): + """Return the activation state of the lenient services.""" + return self.__dict__["enable"] + + @enable.setter + def enable(self, state): + """ + Set the activate state of the lenient services. + + Setting the state to `False` disables all lenient services, and + setting the state to `True` enables all lenient services. + + Args: + + * state (bool): + Activate state for lenient services. + + """ + if not isinstance(state, bool): + cls = self.__class__.__name__ + emsg = f"Invalid {cls!r} option 'enable', got {state!r}." + raise ValueError(emsg) + self.__dict__["enable"] = state + def register_client(self, func, services): """ Add the provided mapping of lenient client function/method to diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index dc4d5de6b5..552f6606d8 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -10,7 +10,7 @@ from functools import wraps import re -from .lenient import LENIENT, lenient_service +from .lenient import LENIENT, lenient_service, qualname __all__ = [ @@ -39,12 +39,11 @@ class _NamedTupleMeta(ABCMeta): """ def __new__(mcs, name, bases, namespace): - token = "_members" names = [] for base in bases: - if hasattr(base, token): - base_names = getattr(base, token) + if hasattr(base, "_members"): + base_names = getattr(base, "_members") is_abstract = getattr( base_names, "__isabstractmethod__", False ) @@ -55,10 +54,10 @@ def __new__(mcs, name, bases, namespace): base_names = (base_names,) names.extend(base_names) - if token in namespace and not getattr( - namespace[token], "__isabstractmethod__", False + if "_members" in namespace and not getattr( + namespace["_members"], "__isabstractmethod__", False ): - namespace_names = namespace[token] + namespace_names = namespace["_members"] if (not isinstance(namespace_names, Iterable)) or isinstance( namespace_names, str @@ -194,6 +193,7 @@ def func(field): result = left return result + # Note that, we use "_members" not "_fields". return [func(field) for field in BaseMetadata._members] @staticmethod @@ -382,7 +382,8 @@ def combine(self, other, lenient=None): if lenient: args, kwargs = (self.combine,), dict() else: - args, kwargs = (), {self.combine: False} + # Require to qualname the method to make it a hashable key. + args, kwargs = (), {qualname(self.combine): False} with LENIENT.context(*args, **kwargs): values = self._combine(other) @@ -427,7 +428,8 @@ def difference(self, other, lenient=None): if lenient: args, kwargs = (self.difference,), dict() else: - args, kwargs = (), {self.difference: False} + # Require to qualname the method to make it a hashable key. + args, kwargs = (), {qualname(self.difference): False} with LENIENT.context(*args, **kwargs): values = self._difference(other) @@ -460,7 +462,8 @@ def equal(self, other, lenient=None): if lenient: args, kwargs = (self.equal,), dict() else: - args, kwargs = (), {self.equal: False} + # Require to qualname the method to make it a hashable key. + args, kwargs = (), {qualname(self.equal): False} with LENIENT.context(*args, **kwargs): result = self.__eq__(other) diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index cdd58da34d..bee6129a6a 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -15,7 +15,7 @@ from collections import Iterable from iris.common.lenient import ( - LENIENT_ENABLE, + LENIENT_ENABLE_DEFAULT, LENIENT_PROTECTED, Lenient, qualname, @@ -24,7 +24,7 @@ class Test___init__(tests.IrisTest): def setUp(self): - self.expected = dict(active=None, enable=LENIENT_ENABLE) + self.expected = dict(active=None, enable=LENIENT_ENABLE_DEFAULT) def test_default(self): lenient = Lenient() @@ -323,96 +323,6 @@ def service(): _ = self.lenient[service] -class Test___setattr__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test_not_in(self): - emsg = "Invalid .* option, got 'wibble'." - with self.assertRaisesRegex(AttributeError, emsg): - self.lenient.wibble = None - - def test_in_value_str(self): - client = "client" - service = "service" - self.lenient.__dict__[client] = None - self.lenient.client = service - self.assertEqual(self.lenient.__dict__[client], (service,)) - - def test_in_value_callable(self): - def service(): - pass - - client = "client" - qualname_service = qualname(service) - self.lenient.__dict__[client] = None - self.lenient.client = service - self.assertEqual(self.lenient.__dict__[client], (qualname_service,)) - - def test_in_value_bool(self): - client = "client" - self.lenient.__dict__[client] = None - self.lenient.client = True - self.assertTrue(self.lenient.__dict__[client]) - self.assertFalse(isinstance(self.lenient.__dict__[client], Iterable)) - - def test_in_value_iterable(self): - client = "client" - services = ("service1", "service2") - self.lenient.__dict__[client] = None - self.lenient.client = services - self.assertEqual(self.lenient.__dict__[client], services) - - def test_in_value_iterable_callable(self): - def service1(): - pass - - def service2(): - pass - - client = "client" - self.lenient.__dict__[client] = None - qualname_services = (qualname(service1), qualname(service2)) - self.lenient.client = (service1, service2) - self.assertEqual(self.lenient.__dict__[client], qualname_services) - - def test_active_iterable(self): - self.assertIsNone(self.lenient.__dict__["active"]) - emsg = "Invalid .* option 'active'" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.active = (None,) - - def test_active_str(self): - active = "active" - client = "client1" - self.assertIsNone(self.lenient.__dict__[active]) - self.lenient.active = client - self.assertEqual(self.lenient.__dict__[active], client) - - def test_active_callable(self): - def client(): - pass - - active = "active" - qualname_client = qualname(client) - self.assertIsNone(self.lenient.__dict__[active]) - self.lenient.active = client - self.assertEqual(self.lenient.__dict__[active], qualname_client) - - def test_enable(self): - enable = "enable" - self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE) - self.lenient.enable = True - self.assertTrue(self.lenient.__dict__[enable]) - self.lenient.enable = False - self.assertFalse(self.lenient.__dict__[enable]) - - def test_enable_invalid(self): - emsg = "Invalid .* option 'enable'" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.enable = None - - class Test___setitem__(tests.IrisTest): def setUp(self): self.lenient = Lenient() @@ -557,7 +467,9 @@ def client(): def test_enable(self): enable = "enable" - self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE) + self.assertEqual( + self.lenient.__dict__[enable], LENIENT_ENABLE_DEFAULT + ) self.lenient[enable] = True self.assertTrue(self.lenient.__dict__[enable]) self.lenient[enable] = False @@ -572,7 +484,7 @@ def test_enable_invalid(self): class Test_context(tests.IrisTest): def setUp(self): self.lenient = Lenient() - self.default = dict(active=None, enable=LENIENT_ENABLE) + self.default = dict(active=None, enable=LENIENT_ENABLE_DEFAULT) def copy(self): return self.lenient.__dict__.copy() @@ -674,6 +586,24 @@ def test_context_runtime(self): self.assertEqual(post, self.default) +class Test_enable(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_getter(self): + self.assertEqual(self.lenient.enable, LENIENT_ENABLE_DEFAULT) + + def test_setter_invalid(self): + emsg = "Invalid .* option 'enable'" + with self.assertRaisesRegex(ValueError, emsg): + self.lenient.enable = 0 + + def test_setter(self): + self.assertEqual(self.lenient.enable, LENIENT_ENABLE_DEFAULT) + self.lenient.enable = False + self.assertFalse(self.lenient.enable) + + class Test_register_client(tests.IrisTest): def setUp(self): self.lenient = Lenient() From f14f3df560047f13e8df48f00a13e4a2d4fd4ccd Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 16 Jun 2020 00:14:14 +0100 Subject: [PATCH 14/45] add BaseMetadata compare test coverage --- lib/iris/common/lenient.py | 13 +- lib/iris/common/metadata.py | 46 +- .../tests/unit/common/lenient/test_Lenient.py | 25 +- .../unit/common/metadata/test_BaseMetadata.py | 440 ++++++++++++++++-- 4 files changed, 454 insertions(+), 70 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index e2d89a5f17..4011e6a6d1 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -407,7 +407,7 @@ def enable(self, state): raise ValueError(emsg) self.__dict__["enable"] = state - def register_client(self, func, services): + def register_client(self, func, services, append=False): """ Add the provided mapping of lenient client function/method to required lenient service function/methods. @@ -422,6 +422,12 @@ def register_client(self, func, services): One or more service function/methods or fully qualified string names of the required service function/method. + Kwargs: + + * append (bool): + If True, append the lenient services to any pre-registered lenient + services for the provided lenient client. Default is False. + """ func = qualname(func) cls = self.__class__.__name__ @@ -437,6 +443,11 @@ def register_client(self, func, services): emsg = f"Require at least one {cls!r} lenient client service." raise ValueError(emsg) services = tuple([qualname(service) for service in services]) + if append: + # Service order is not significant, therefore there is no + # requirement to preserve it. + existing = self.__dict__[func] if func in self else () + services = tuple(sorted(set(existing) | set(services))) self.__dict__[func] = services def register_service(self, func): diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 552f6606d8..f40c76f21e 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -156,14 +156,14 @@ def func(field): value = getattr(self, field) return value if value == getattr(other, field) else None - # Note that, we use "_fields" not "_members". + # Note that, for strict we use "_fields" not "_members". values = [func(field) for field in self._fields] return values def _combine_lenient(self, other): """ - Perform lenient metadata member combination. + Perform lenient combination of metadata members. Args: @@ -221,7 +221,7 @@ def _combine_lenient_attributes(left, right): def _compare_lenient(self, other): """ - Support lenient metadata equality for coordinates. + Perform lenient equality of metadata memberss. Args: @@ -262,14 +262,14 @@ def func(field): result = None if left == right else (left, right) return result - # Note that, we use "_fields" not "_members". + # Note that, for strict we use "_fields" not "_members". values = [func(field) for field in self._fields] return values def _difference_lenient(self, other): """ - Perform lenient metadata member difference. + Perform lenient difference of metadata members. Args: @@ -380,9 +380,11 @@ def combine(self, other, lenient=None): values = self._combine(other) else: if lenient: - args, kwargs = (self.combine,), dict() + # Use qualname to disassociate from the instance bounded method. + args, kwargs = (qualname(self.combine),), dict() else: - # Require to qualname the method to make it a hashable key. + # Use qualname to guarantee that the instance bounded method + # is a hashable key. args, kwargs = (), {qualname(self.combine): False} with LENIENT.context(*args, **kwargs): @@ -426,9 +428,11 @@ def difference(self, other, lenient=None): values = self._difference(other) else: if lenient: - args, kwargs = (self.difference,), dict() + # Use qualname to disassociate from the instance bounded method. + args, kwargs = (qualname(self.difference),), dict() else: - # Require to qualname the method to make it a hashable key. + # Use qualname to guarantee that the instance bounded method + # is a hashable key. args, kwargs = (), {qualname(self.difference): False} with LENIENT.context(*args, **kwargs): @@ -460,9 +464,11 @@ def equal(self, other, lenient=None): result = self.__eq__(other) else: if lenient: - args, kwargs = (self.equal,), dict() + # Use qualname to disassociate from the instance bounded method. + args, kwargs = (qualname(self.equal),), dict() else: - # Require to qualname the method to make it a hashable key. + # Use qualname to guarantee that the instance bounded method + # is a hashable key. args, kwargs = (), {qualname(self.equal): False} with LENIENT.context(*args, **kwargs): @@ -580,7 +586,7 @@ def __eq__(self, other): def _combine_lenient(self, other): """ - Perform lenient metadata member combination for cell measures. + Perform lenient combination of metadata members for cell measures. Args: @@ -602,7 +608,7 @@ def _combine_lenient(self, other): def _compare_lenient(self, other): """ - Perform lenient metadata equality for cell measures. + Perform lenient equality of metadata members for cell measures. Args: @@ -624,7 +630,7 @@ def _compare_lenient(self, other): def _difference_lenient(self, other): """ - Perform lenient metadata member difference for cell measures. + Perform lenient difference of metadata members for cell measures. Args: @@ -681,7 +687,7 @@ def __eq__(self, other): def _combine_lenient(self, other): """ - Perform lenient metadata member combination for coordinates. + Perform lenient combination of metadata members for coordinates. Args: @@ -707,7 +713,7 @@ def func(field): def _compare_lenient(self, other): """ - Perform lenient metadata equality for coordinates. + Perform lenient equality of metadata members for coordinates. Args: @@ -732,7 +738,7 @@ def _compare_lenient(self, other): def _difference_lenient(self, other): """ - Perform lenient metadata member difference for coordinates. + Perform lenient differnece of metadata members for coordinates. Args: @@ -790,7 +796,7 @@ def __eq__(self, other): def _combine_lenient(self, other): """ - Perform lenient metadata member combination for cubes. + Perform lenient combination of metadata members for cubes. Args: @@ -815,7 +821,7 @@ def _combine_lenient(self, other): def _compare_lenient(self, other): """ - Perform lenient metadata equality for cubes. + Perform lenient equality of metadata members for cubes. Args: @@ -835,7 +841,7 @@ def _compare_lenient(self, other): def _difference_lenient(self, other): """ - Perform lenient metadata member difference for cubes. + Perform lenient difference of metadata members for cubes. Args: diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index bee6129a6a..ab4fe210d8 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -467,9 +467,7 @@ def client(): def test_enable(self): enable = "enable" - self.assertEqual( - self.lenient.__dict__[enable], LENIENT_ENABLE_DEFAULT - ) + self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE_DEFAULT) self.lenient[enable] = True self.assertTrue(self.lenient.__dict__[enable]) self.lenient[enable] = False @@ -666,6 +664,27 @@ def test_services_empty(self): with self.assertRaisesRegex(ValueError, emsg): self.lenient.register_client("client", ()) + def test_services_overwrite(self): + client = "client" + services = ("service1", "service2") + self.lenient.__dict__[client] = services + self.assertEqual(self.lenient[client], services) + new_services = ("service3", "service4") + self.lenient.register_client(client, services=new_services) + self.assertEqual(self.lenient[client], new_services) + + def test_services_append(self): + client = "client" + services = ("service1", "service2") + self.lenient.__dict__[client] = services + self.assertEqual(self.lenient[client], services) + new_services = ("service3", "service4") + self.lenient.register_client( + client, services=new_services, append=True + ) + expected = set(services + new_services) + self.assertEqual(set(self.lenient[client]), expected) + class Test_register_service(tests.IrisTest): def setUp(self): diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 104a220370..402fa73914 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -12,9 +12,12 @@ # importing anything else. import iris.tests as tests +from collections import OrderedDict import unittest.mock as mock +from unittest.mock import sentinel -from iris.common.metadata import BaseMetadata +from iris.common.lenient import LENIENT, qualname +from iris.common.metadata import BaseMetadata, CubeMetadata class Test(tests.IrisTest): @@ -57,6 +60,351 @@ def test__fields(self): self.assertEqual(BaseMetadata._fields, expected) +class Test__combine(tests.IrisTest): + def setUp(self): + self.kwargs = dict( + standard_name="standard_name", + long_name="long_name", + var_name="var_name", + units="units", + attributes=dict(one=1, two=2), + ) + self.metadata = BaseMetadata(**self.kwargs) + + def test_lenient(self): + return_value = sentinel._combine_lenient + other = sentinel.other + with mock.patch( + "iris.common.metadata.LENIENT", return_value=True + ) as mlenient: + with mock.patch.object( + BaseMetadata, "_combine_lenient", return_value=return_value + ) as mcombine: + result = self.metadata._combine(other) + + self.assertEqual(mlenient.call_count, 1) + (args,), kwargs = mlenient.call_args + self.assertEqual(args, self.metadata.combine) + self.assertEqual(kwargs, dict()) + + self.assertEqual(result, return_value) + self.assertEqual(mcombine.call_count, 1) + (args,), kwargs = mcombine.call_args + self.assertEqual(args, other) + self.assertEqual(kwargs, dict()) + + def test_strict(self): + dummy = sentinel.dummy + values = self.kwargs.copy() + values["standard_name"] = dummy + values["var_name"] = dummy + values["attributes"] = dummy + other = BaseMetadata(**values) + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + result = self.metadata._combine(other) + + expected = [ + None if values[field] == dummy else values[field] + for field in BaseMetadata._fields + ] + self.assertEqual(result, expected) + + +class Test__combine_lenient(tests.IrisTest): + def setUp(self): + self.none = BaseMetadata( + *(None,) * len(BaseMetadata._fields) + )._asdict() + self.names = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + ) + + def test_strict_units(self): + left = self.none.copy() + left["units"] = "K" + right = left.copy() + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) + expected = list(left.values()) + self.assertEqual(expected, result) + + def test_strict_units_different(self): + left = self.none.copy() + right = self.none.copy() + left["units"] = "K" + right["units"] = "km" + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) + expected = list(self.none.values()) + self.assertEqual(expected, result) + + def test_strict_units_different_none(self): + left = self.none.copy() + right = self.none.copy() + left["units"] = "K" + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) + expected = list(self.none.values()) + self.assertEqual(expected, result) + result = rmetadata._combine_lenient(lmetadata) + self.assertEqual(expected, result) + + def test_attributes(self): + left = self.none.copy() + right = self.none.copy() + ldict = dict(item=sentinel.left) + rdict = dict(item=sentinel.right) + left["attributes"] = ldict + right["attributes"] = rdict + rmetadata = BaseMetadata(**right) + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, + "_combine_lenient_attributes", + return_value=return_value, + ) as mocker: + lmetadata = BaseMetadata(**left) + result = lmetadata._combine_lenient(rmetadata) + + expected = self.none.copy() + expected["attributes"] = return_value + expected = list(expected.values()) + self.assertEqual(expected, result) + + self.assertEqual(mocker.call_count, 1) + args, kwargs = mocker.call_args + expected = (ldict, rdict) + self.assertEqual(expected, args) + + def test_attributes_non_mapping_different(self): + left = self.none.copy() + right = self.none.copy() + ldict = dict(item=sentinel.left) + rdict = sentinel.right + left["attributes"] = ldict + right["attributes"] = rdict + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) + expected = list(self.none.copy().values()) + self.assertEqual(expected, result) + + def test_attributes_non_mapping_different_none(self): + left = self.none.copy() + right = self.none.copy() + ldict = dict(item=sentinel.left) + left["attributes"] = ldict + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._combine_lenient(rmetadata) + expected = self.none.copy() + expected["attributes"] = ldict + expected = list(expected.values()) + self.assertEqual(expected, result) + + result = rmetadata._combine_lenient(lmetadata) + self.assertEqual(expected, result) + + def test_names(self): + left = self.none.copy() + left.update(self.names) + right = left.copy() + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) + expected = list(left.values()) + self.assertEqual(expected, result) + + def test_names_different(self): + dummy = sentinel.dummy + left = self.none.copy() + right = self.none.copy() + left.update(self.names) + right["standard_name"] = dummy + right["long_name"] = dummy + right["var_name"] = dummy + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) + expected = list(self.none.copy().values()) + self.assertEqual(expected, result) + + def test_names_different_none(self): + left = self.none.copy() + right = self.none.copy() + left.update(self.names) + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._combine_lenient(rmetadata) + expected = list(left.values()) + self.assertEqual(expected, result) + + result = rmetadata._combine_lenient(lmetadata) + self.assertEqual(expected, result) + + +class Test__combine_lenient_attributes(tests.IrisTest): + def setUp(self): + self.values = OrderedDict( + one=sentinel.one, + two=sentinel.two, + three=sentinel.three, + four=sentinel.four, + five=sentinel.five, + ) + self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.dummy = sentinel.dummy + + def test_same(self): + left = self.values.copy() + right = self.values.copy() + result = self.metadata._combine_lenient_attributes(left, right) + expected = dict(**left) + self.assertEqual(expected, result) + + def test_different(self): + left = self.values.copy() + right = self.values.copy() + left["two"] = left["four"] = self.dummy + result = self.metadata._combine_lenient_attributes(left, right) + expected = self.values.copy() + for key in ["two", "four"]: + del expected[key] + self.assertEqual(dict(expected), result) + + def test_different_none(self): + left = self.values.copy() + right = self.values.copy() + left["one"] = left["three"] = left["five"] = self.dummy + result = self.metadata._combine_lenient_attributes(left, right) + expected = self.values.copy() + for key in ["one", "three", "five"]: + del expected[key] + self.assertEqual(dict(expected), result) + + def test_extra(self): + left = self.values.copy() + right = self.values.copy() + left["extra_left"] = sentinel.extra_left + right["extra_right"] = sentinel.extra_right + result = self.metadata._combine_lenient_attributes(left, right) + expected = self.values.copy() + expected["extra_left"] = left["extra_left"] + expected["extra_right"] = right["extra_right"] + self.assertEqual(dict(expected), result) + + +class Test_combine(tests.IrisTest): + def setUp(self): + kwargs = dict( + standard_name="standard_name", + long_name="long_name", + var_name="var_name", + units="units", + attributes="attributes", + ) + self.metadata = BaseMetadata(**kwargs) + self.mock_kwargs = OrderedDict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + + def test_lenient_service(self): + qualname_combine = qualname(BaseMetadata.combine) + self.assertIn(qualname_combine, LENIENT) + self.assertTrue(LENIENT[qualname_combine]) + + def test_cannot_combine_non_class(self): + emsg = "Cannot combine" + with self.assertRaisesRegex(ValueError, emsg): + self.metadata.combine(None) + + def test_cannot_combine(self): + other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) + emsg = "Cannot combine" + with self.assertRaisesRegex(ValueError, emsg): + self.metadata.combine(other) + + def test_lenient_default(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_combine", return_value=return_value + ) as mocker: + result = self.metadata.combine(self.metadata) + + self.assertEqual(result._asdict(), self.mock_kwargs) + self.assertEqual(mocker.call_count, 1) + (args,), kwargs = mocker.call_args + self.assertEqual(id(args), id(self.metadata)) + self.assertEqual(kwargs, dict()) + + def test_lenient_true(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_combine", return_value=return_value + ) as mcombine: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.combine(self.metadata, lenient=True) + + self.assertEqual(mcontext.call_count, 1) + (args,), kwargs = mcontext.call_args + self.assertEqual(args, qualname(BaseMetadata.combine)) + self.assertEqual(kwargs, dict()) + + self.assertEqual(result._asdict(), self.mock_kwargs) + self.assertEqual(mcombine.call_count, 1) + (args,), kwargs = mcombine.call_args + self.assertEqual(id(args), id(self.metadata)) + self.assertEqual(kwargs, dict()) + + def test_lenient_false(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_combine", return_value=return_value + ) as mcombine: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.combine(self.metadata, lenient=False) + + self.assertEqual(mcontext.call_count, 1) + args, kwargs = mcontext.call_args + self.assertEqual(args, ()) + self.assertEqual(kwargs, {qualname(BaseMetadata.combine): False}) + + self.assertEqual(result._asdict(), self.mock_kwargs) + self.assertEqual(mcombine.call_count, 1) + (args,), kwargs = mcombine.call_args + self.assertEqual(id(args), id(self.metadata)) + self.assertEqual(kwargs, dict()) + + +class Test__is_attributes(tests.IrisTest): + def setUp(self): + self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.field = "attributes" + + def test_field(self): + self.assertTrue(self.metadata._is_attributes(self.field, {}, {})) + + def test_field_not_attributes(self): + self.assertFalse(self.metadata._is_attributes(None, {}, {})) + + def test_left_not_mapping(self): + self.assertFalse(self.metadata._is_attributes(self.field, None, {})) + + def test_right_not_mapping(self): + self.assertFalse(self.metadata._is_attributes(self.field, {}, None)) + + class Test___lt__(tests.IrisTest): def setUp(self): self.one = BaseMetadata(1, 1, 1, 1, 1) @@ -80,51 +428,6 @@ def test__none_lhs_operand(self): self.assertTrue(result) -class Test_token(tests.IrisTest): - def test_passthru_None(self): - result = BaseMetadata.token(None) - self.assertIsNone(result) - - def test_fail_leading_underscore(self): - result = BaseMetadata.token("_nope") - self.assertIsNone(result) - - def test_fail_leading_dot(self): - result = BaseMetadata.token(".nope") - self.assertIsNone(result) - - def test_fail_leading_plus(self): - result = BaseMetadata.token("+nope") - self.assertIsNone(result) - - def test_fail_leading_at(self): - result = BaseMetadata.token("@nope") - self.assertIsNone(result) - - def test_fail_space(self): - result = BaseMetadata.token("nope nope") - self.assertIsNone(result) - - def test_fail_colon(self): - result = BaseMetadata.token("nope:") - self.assertIsNone(result) - - def test_pass_simple(self): - token = "simple" - result = BaseMetadata.token(token) - self.assertEqual(result, token) - - def test_pass_leading_digit(self): - token = "123simple" - result = BaseMetadata.token(token) - self.assertEqual(result, token) - - def test_pass_mixture(self): - token = "S.imple@one+two_3" - result = BaseMetadata.token(token) - self.assertEqual(result, token) - - class Test_name(tests.IrisTest): def setUp(self): self.default = BaseMetadata.DEFAULT_NAME @@ -204,5 +507,50 @@ def test_default__invalid_token(self): metadata.name(default=token, token=True) +class Test_token(tests.IrisTest): + def test_passthru_None(self): + result = BaseMetadata.token(None) + self.assertIsNone(result) + + def test_fail_leading_underscore(self): + result = BaseMetadata.token("_nope") + self.assertIsNone(result) + + def test_fail_leading_dot(self): + result = BaseMetadata.token(".nope") + self.assertIsNone(result) + + def test_fail_leading_plus(self): + result = BaseMetadata.token("+nope") + self.assertIsNone(result) + + def test_fail_leading_at(self): + result = BaseMetadata.token("@nope") + self.assertIsNone(result) + + def test_fail_space(self): + result = BaseMetadata.token("nope nope") + self.assertIsNone(result) + + def test_fail_colon(self): + result = BaseMetadata.token("nope:") + self.assertIsNone(result) + + def test_pass_simple(self): + token = "simple" + result = BaseMetadata.token(token) + self.assertEqual(result, token) + + def test_pass_leading_digit(self): + token = "123simple" + result = BaseMetadata.token(token) + self.assertEqual(result, token) + + def test_pass_mixture(self): + token = "S.imple@one+two_3" + result = BaseMetadata.token(token) + self.assertEqual(result, token) + + if __name__ == "__main__": tests.main() From 9bcea00fb6691754085f8ee9cd5861e8da414057 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 16 Jun 2020 01:28:33 +0100 Subject: [PATCH 15/45] metadata rationalisation --- lib/iris/common/metadata.py | 127 +++++++++--------- .../unit/common/metadata/test_BaseMetadata.py | 4 +- 2 files changed, 69 insertions(+), 62 deletions(-) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index f40c76f21e..55cdb10042 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -219,6 +219,60 @@ def _combine_lenient_attributes(left, right): return result + def _common( + self, other, func_service, func_operation, action, lenient=None + ): + """ + + Args: + + * other (metadata): + A metadata instance of the same type. + + * func_service (callable): + The parent service method offering the API entry-point to the service. + + * func_operation (callable): + The parent service method that provides the actual service. + + * action (str): + The verb describing the service operation. + + Kwargs: + + * lenient (boolean): + Enable/disable the lenient service operation. The default is to automatically + detect whether this lenient service operation is enabled. + + Returns: + The result of the service operation to the parent service caller. + + """ + if ( + not hasattr(other, "__class__") + or other.__class__ is not self.__class__ + ): + emsg = "Cannot {} {!r} with {!r}." + raise TypeError( + emsg.format(action, self.__class__.__name__, type(other)) + ) + + if lenient is None: + result = func_operation(other) + else: + if lenient: + # Use qualname to disassociate from the instance bounded method. + args, kwargs = (qualname(func_service),), dict() + else: + # Use qualname to guarantee that the instance bounded method + # is a hashable key. + args, kwargs = (), {qualname(func_service): False} + + with LENIENT.context(*args, **kwargs): + result = func_operation(other) + + return result + def _compare_lenient(self, other): """ Perform lenient equality of metadata memberss. @@ -369,28 +423,10 @@ def combine(self, other, lenient=None): Metadata instance. """ - if ( - not hasattr(other, "__class__") - or other.__class__ is not self.__class__ - ): - emsg = "Cannot combine {!r} with {!r}." - raise ValueError(emsg.format(self.__class__.__name__, type(other))) - - if lenient is None: - values = self._combine(other) - else: - if lenient: - # Use qualname to disassociate from the instance bounded method. - args, kwargs = (qualname(self.combine),), dict() - else: - # Use qualname to guarantee that the instance bounded method - # is a hashable key. - args, kwargs = (), {qualname(self.combine): False} - - with LENIENT.context(*args, **kwargs): - values = self._combine(other) - - return self.__class__(*values) + result = self._common( + other, self.combine, self._combine, "combine", lenient=lenient + ) + return self.__class__(*result) @lenient_service def difference(self, other, lenient=None): @@ -398,7 +434,7 @@ def difference(self, other, lenient=None): Return a new metadata instance created by performing a difference comparison between each of the associated metadata members. - A returned metadata member with a value of "None" indicates that there + A metadata member returned with a value of "None" indicates that there is no difference between the members being compared. Otherwise, a tuple of the different values is returned. @@ -417,28 +453,10 @@ def difference(self, other, lenient=None): Metadata instance. """ - if ( - not hasattr(other, "__class__") - or other.__class__ is not self.__class__ - ): - emsg = "Cannot differ {!r} with {!r}." - raise ValueError(emsg.format(self.__class__.__name__, type(other))) - - if lenient is None: - values = self._difference(other) - else: - if lenient: - # Use qualname to disassociate from the instance bounded method. - args, kwargs = (qualname(self.difference),), dict() - else: - # Use qualname to guarantee that the instance bounded method - # is a hashable key. - args, kwargs = (), {qualname(self.difference): False} - - with LENIENT.context(*args, **kwargs): - values = self._difference(other) - - return self.__class__(*values) + result = self._common( + other, self.difference, self._difference, "differ", lenient=lenient + ) + return self.__class__(*result) @lenient_service def equal(self, other, lenient=None): @@ -460,20 +478,9 @@ def equal(self, other, lenient=None): Boolean. """ - if lenient is None: - result = self.__eq__(other) - else: - if lenient: - # Use qualname to disassociate from the instance bounded method. - args, kwargs = (qualname(self.equal),), dict() - else: - # Use qualname to guarantee that the instance bounded method - # is a hashable key. - args, kwargs = (), {qualname(self.equal): False} - - with LENIENT.context(*args, **kwargs): - result = self.__eq__(other) - + result = self._common( + other, self.equal, self.__eq__, "compare", lenient=lenient + ) return result def name(self, default=None, token=False): @@ -738,7 +745,7 @@ def _compare_lenient(self, other): def _difference_lenient(self, other): """ - Perform lenient differnece of metadata members for coordinates. + Perform lenient difference of metadata members for coordinates. Args: diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 402fa73914..302feaa24b 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -326,13 +326,13 @@ def test_lenient_service(self): def test_cannot_combine_non_class(self): emsg = "Cannot combine" - with self.assertRaisesRegex(ValueError, emsg): + with self.assertRaisesRegex(TypeError, emsg): self.metadata.combine(None) def test_cannot_combine(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) emsg = "Cannot combine" - with self.assertRaisesRegex(ValueError, emsg): + with self.assertRaisesRegex(TypeError, emsg): self.metadata.combine(other) def test_lenient_default(self): From 30b787301b19adc91ebc7e425d7b22ed309d2e09 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 18 Jun 2020 13:45:07 +0100 Subject: [PATCH 16/45] add BaseMetadata difference test coverage --- lib/iris/common/lenient.py | 6 +- lib/iris/common/metadata.py | 121 ++-- .../tests/unit/common/lenient/test_Lenient.py | 2 +- .../unit/common/metadata/test_BaseMetadata.py | 651 ++++++++++++++++-- 4 files changed, 648 insertions(+), 132 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 4011e6a6d1..afe64edf9d 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -89,7 +89,7 @@ def func(): (func,) = dargs @wraps(func) - def lenient_inner(*args, **kwargs): + def lenient_inner_naked(*args, **kwargs): """ Closure wrapper function to register the wrapped function/method as active at runtime before executing it. @@ -99,7 +99,7 @@ def lenient_inner(*args, **kwargs): result = func(*args, **kwargs) return result - result = lenient_inner + result = lenient_inner_naked else: # The decorator has been called with None, zero or more explicit lenient services. if services is None: @@ -372,7 +372,7 @@ def context(self, *args, **kwargs): if args: active = self.__dict__["active"] if active is None: - active = "context" + active = "_context" self.__dict__["active"] = active self.__dict__[active] = tuple([qualname(arg) for arg in args]) try: diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 55cdb10042..b3d4b9af43 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -144,6 +144,61 @@ def __ne__(self, other): return result + def _api_common( + self, other, func_service, func_operation, action, lenient=None + ): + """ + Common entry-point for API facing lenient service methods. + + Args: + + * other (metadata): + A metadata instance of the same type. + + * func_service (callable): + The parent service method offering the API entry-point to the service. + + * func_operation (callable): + The parent service method that provides the actual service. + + * action (str): + The verb describing the service operation. + + Kwargs: + + * lenient (boolean): + Enable/disable the lenient service operation. The default is to automatically + detect whether this lenient service operation is enabled. + + Returns: + The result of the service operation to the parent service caller. + + """ + if ( + not hasattr(other, "__class__") + or other.__class__ is not self.__class__ + ): + emsg = "Cannot {} {!r} with {!r}." + raise TypeError( + emsg.format(action, self.__class__.__name__, type(other)) + ) + + if lenient is None: + result = func_operation(other) + else: + if lenient: + # Use qualname to disassociate from the instance bounded method. + args, kwargs = (qualname(func_service),), dict() + else: + # Use qualname to guarantee that the instance bounded method + # is a hashable key. + args, kwargs = (), {qualname(func_service): False} + + with LENIENT.context(*args, **kwargs): + result = func_operation(other) + + return result + def _combine(self, other): """Perform associated metadata member combination.""" if LENIENT(self.combine): @@ -201,13 +256,13 @@ def _combine_lenient_attributes(left, right): """Leniently combine the dictionary members together.""" sleft = set(left.items()) sright = set(right.items()) - # Union of common items. + # Intersection of common items. common = sleft & sright # Items in sleft different from sright. dsleft = dict(sleft - sright) # Items in sright different from sleft. dsright = dict(sright - sleft) - # Union of common item keys with different values. + # Intersection of common item keys with different values. keys = set(dsleft.keys()) & set(dsright.keys()) # Remove (in-place) common item keys with different values. [dsleft.pop(key) for key in keys] @@ -219,60 +274,6 @@ def _combine_lenient_attributes(left, right): return result - def _common( - self, other, func_service, func_operation, action, lenient=None - ): - """ - - Args: - - * other (metadata): - A metadata instance of the same type. - - * func_service (callable): - The parent service method offering the API entry-point to the service. - - * func_operation (callable): - The parent service method that provides the actual service. - - * action (str): - The verb describing the service operation. - - Kwargs: - - * lenient (boolean): - Enable/disable the lenient service operation. The default is to automatically - detect whether this lenient service operation is enabled. - - Returns: - The result of the service operation to the parent service caller. - - """ - if ( - not hasattr(other, "__class__") - or other.__class__ is not self.__class__ - ): - emsg = "Cannot {} {!r} with {!r}." - raise TypeError( - emsg.format(action, self.__class__.__name__, type(other)) - ) - - if lenient is None: - result = func_operation(other) - else: - if lenient: - # Use qualname to disassociate from the instance bounded method. - args, kwargs = (qualname(func_service),), dict() - else: - # Use qualname to guarantee that the instance bounded method - # is a hashable key. - args, kwargs = (), {qualname(func_service): False} - - with LENIENT.context(*args, **kwargs): - result = func_operation(other) - - return result - def _compare_lenient(self, other): """ Perform lenient equality of metadata memberss. @@ -363,7 +364,7 @@ def _difference_lenient_attributes(left, right): dsleft = dict(sleft - sright) # Items in sright different from sleft. dsright = dict(sright - sleft) - # Union of common item keys with different values. + # Intersection of common item keys with different values. keys = set(dsleft.keys()) & set(dsright.keys()) # Keep (in-place) common item keys with different values. [dsleft.pop(key) for key in list(dsleft.keys()) if key not in keys] @@ -423,7 +424,7 @@ def combine(self, other, lenient=None): Metadata instance. """ - result = self._common( + result = self._api_common( other, self.combine, self._combine, "combine", lenient=lenient ) return self.__class__(*result) @@ -453,7 +454,7 @@ def difference(self, other, lenient=None): Metadata instance. """ - result = self._common( + result = self._api_common( other, self.difference, self._difference, "differ", lenient=lenient ) return self.__class__(*result) @@ -478,7 +479,7 @@ def equal(self, other, lenient=None): Boolean. """ - result = self._common( + result = self._api_common( other, self.equal, self.__eq__, "compare", lenient=lenient ) return result diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index ab4fe210d8..5d6eedcde8 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -579,7 +579,7 @@ def test_context_runtime(self): post = self.copy() self.assertEqual(pre, self.default) expected = self.default.copy() - expected.update(dict(active="context", context=services)) + expected.update(dict(active="_context", _context=services)) self.assertEqual(context, expected) self.assertEqual(post, self.default) diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 302feaa24b..b2476239c9 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -57,7 +57,30 @@ def test__fields(self): "units", "attributes", ) - self.assertEqual(BaseMetadata._fields, expected) + self.assertEqual(expected, BaseMetadata._fields) + + +class Test___lt__(tests.IrisTest): + def setUp(self): + self.one = BaseMetadata(1, 1, 1, 1, 1) + self.two = BaseMetadata(1, 1, 1, 1, 2) + self.none = BaseMetadata(1, 1, 1, 1, None) + + def test__ascending_lt(self): + result = self.one < self.two + self.assertTrue(result) + + def test__descending_lt(self): + result = self.two < self.one + self.assertFalse(result) + + def test__none_rhs_operand(self): + result = self.one < self.none + self.assertFalse(result) + + def test__none_lhs_operand(self): + result = self.none < self.one + self.assertTrue(result) class Test__combine(tests.IrisTest): @@ -67,7 +90,7 @@ def setUp(self): long_name="long_name", var_name="var_name", units="units", - attributes=dict(one=1, two=2), + attributes=dict(one=sentinel.one, two=sentinel.two), ) self.metadata = BaseMetadata(**self.kwargs) @@ -82,16 +105,16 @@ def test_lenient(self): ) as mcombine: result = self.metadata._combine(other) - self.assertEqual(mlenient.call_count, 1) + self.assertEqual(1, mlenient.call_count) (args,), kwargs = mlenient.call_args - self.assertEqual(args, self.metadata.combine) - self.assertEqual(kwargs, dict()) + self.assertEqual(self.metadata.combine, args) + self.assertEqual(dict(), kwargs) - self.assertEqual(result, return_value) - self.assertEqual(mcombine.call_count, 1) + self.assertEqual(return_value, result) + self.assertEqual(1, mcombine.call_count) (args,), kwargs = mcombine.call_args - self.assertEqual(args, other) - self.assertEqual(kwargs, dict()) + self.assertEqual(other, args) + self.assertEqual(dict(), kwargs) def test_strict(self): dummy = sentinel.dummy @@ -107,7 +130,7 @@ def test_strict(self): None if values[field] == dummy else values[field] for field in BaseMetadata._fields ] - self.assertEqual(result, expected) + self.assertEqual(expected, result) class Test__combine_lenient(tests.IrisTest): @@ -127,6 +150,7 @@ def test_strict_units(self): right = left.copy() lmetadata = BaseMetadata(**left) rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) self.assertEqual(expected, result) @@ -138,9 +162,12 @@ def test_strict_units_different(self): right["units"] = "km" lmetadata = BaseMetadata(**left) rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.values()) self.assertEqual(expected, result) + result = rmetadata._combine_lenient(lmetadata) + self.assertEqual(expected, result) def test_strict_units_different_none(self): left = self.none.copy() @@ -148,9 +175,11 @@ def test_strict_units_different_none(self): left["units"] = "K" lmetadata = BaseMetadata(**left) rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.values()) self.assertEqual(expected, result) + result = rmetadata._combine_lenient(lmetadata) self.assertEqual(expected, result) @@ -176,10 +205,11 @@ def test_attributes(self): expected = list(expected.values()) self.assertEqual(expected, result) - self.assertEqual(mocker.call_count, 1) + self.assertEqual(1, mocker.call_count) args, kwargs = mocker.call_args expected = (ldict, rdict) self.assertEqual(expected, args) + self.assertEqual(dict(), kwargs) def test_attributes_non_mapping_different(self): left = self.none.copy() @@ -190,6 +220,7 @@ def test_attributes_non_mapping_different(self): right["attributes"] = rdict lmetadata = BaseMetadata(**left) rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.copy().values()) self.assertEqual(expected, result) @@ -217,6 +248,7 @@ def test_names(self): right = left.copy() lmetadata = BaseMetadata(**left) rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) self.assertEqual(expected, result) @@ -231,6 +263,7 @@ def test_names_different(self): right["var_name"] = dummy lmetadata = BaseMetadata(**left) rmetadata = BaseMetadata(**right) + result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.copy().values()) self.assertEqual(expected, result) @@ -265,6 +298,7 @@ def setUp(self): def test_same(self): left = self.values.copy() right = self.values.copy() + result = self.metadata._combine_lenient_attributes(left, right) expected = dict(**left) self.assertEqual(expected, result) @@ -273,6 +307,7 @@ def test_different(self): left = self.values.copy() right = self.values.copy() left["two"] = left["four"] = self.dummy + result = self.metadata._combine_lenient_attributes(left, right) expected = self.values.copy() for key in ["two", "four"]: @@ -282,7 +317,8 @@ def test_different(self): def test_different_none(self): left = self.values.copy() right = self.values.copy() - left["one"] = left["three"] = left["five"] = self.dummy + left["one"] = left["three"] = left["five"] = None + result = self.metadata._combine_lenient_attributes(left, right) expected = self.values.copy() for key in ["one", "three", "five"]: @@ -294,6 +330,7 @@ def test_extra(self): right = self.values.copy() left["extra_left"] = sentinel.extra_left right["extra_right"] = sentinel.extra_right + result = self.metadata._combine_lenient_attributes(left, right) expected = self.values.copy() expected["extra_left"] = left["extra_left"] @@ -342,11 +379,11 @@ def test_lenient_default(self): ) as mocker: result = self.metadata.combine(self.metadata) - self.assertEqual(result._asdict(), self.mock_kwargs) - self.assertEqual(mocker.call_count, 1) + self.assertEqual(self.mock_kwargs, result._asdict()) + self.assertEqual(1, mocker.call_count) (args,), kwargs = mocker.call_args - self.assertEqual(id(args), id(self.metadata)) - self.assertEqual(kwargs, dict()) + self.assertEqual(id(self.metadata), id(args)) + self.assertEqual(dict(), kwargs) def test_lenient_true(self): return_value = self.mock_kwargs.values() @@ -356,16 +393,16 @@ def test_lenient_true(self): with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.combine(self.metadata, lenient=True) - self.assertEqual(mcontext.call_count, 1) + self.assertEqual(1, mcontext.call_count) (args,), kwargs = mcontext.call_args - self.assertEqual(args, qualname(BaseMetadata.combine)) - self.assertEqual(kwargs, dict()) + self.assertEqual(qualname(BaseMetadata.combine), args) + self.assertEqual(dict(), kwargs) self.assertEqual(result._asdict(), self.mock_kwargs) - self.assertEqual(mcombine.call_count, 1) + self.assertEqual(1, mcombine.call_count) (args,), kwargs = mcombine.call_args - self.assertEqual(id(args), id(self.metadata)) - self.assertEqual(kwargs, dict()) + self.assertEqual(id(self.metadata), id(args)) + self.assertEqual(dict(), kwargs) def test_lenient_false(self): return_value = self.mock_kwargs.values() @@ -375,17 +412,509 @@ def test_lenient_false(self): with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.combine(self.metadata, lenient=False) - self.assertEqual(mcontext.call_count, 1) + self.assertEqual(1, mcontext.call_count) args, kwargs = mcontext.call_args - self.assertEqual(args, ()) - self.assertEqual(kwargs, {qualname(BaseMetadata.combine): False}) + self.assertEqual((), args) + self.assertEqual({qualname(BaseMetadata.combine): False}, kwargs) - self.assertEqual(result._asdict(), self.mock_kwargs) - self.assertEqual(mcombine.call_count, 1) + self.assertEqual(self.mock_kwargs, result._asdict()) + self.assertEqual(1, mcombine.call_count) (args,), kwargs = mcombine.call_args + self.assertEqual(id(self.metadata), id(args)) + self.assertEqual(dict(), kwargs) + + +class Test__difference(tests.IrisTest): + def setUp(self): + self.kwargs = dict( + standard_name="standard_name", + long_name="long_name", + var_name="var_name", + units="units", + attributes=dict(one=sentinel.one, two=sentinel.two), + ) + self.metadata = BaseMetadata(**self.kwargs) + + def test_lenient(self): + return_value = sentinel._difference_lenient + other = sentinel.other + with mock.patch( + "iris.common.metadata.LENIENT", return_value=True + ) as mlenient: + with mock.patch.object( + BaseMetadata, "_difference_lenient", return_value=return_value + ) as mdifference: + result = self.metadata._difference(other) + + self.assertEqual(1, mlenient.call_count) + (args,), kwargs = mlenient.call_args + self.assertEqual(self.metadata.difference, args) + self.assertEqual(dict(), kwargs) + + self.assertEqual(return_value, result) + self.assertEqual(1, mdifference.call_count) + (args,), kwargs = mdifference.call_args + self.assertEqual(other, args) + self.assertEqual(dict(), kwargs) + + def test_strict(self): + dummy = sentinel.dummy + values = self.kwargs.copy() + values["long_name"] = dummy + values["units"] = dummy + other = BaseMetadata(**values) + method = "_difference_strict_attributes" + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch.object( + BaseMetadata, method, return_value=None + ) as mdifference: + result = self.metadata._difference(other) + + expected = [ + (self.kwargs[field], dummy) if values[field] == dummy else None + for field in BaseMetadata._fields + ] + self.assertEqual(expected, result) + self.assertEqual(1, mdifference.call_count) + args, kwargs = mdifference.call_args + expected = (self.kwargs["attributes"], values["attributes"]) + self.assertEqual(expected, args) + self.assertEqual(dict(), kwargs) + + with mock.patch.object( + BaseMetadata, method, return_value=None + ) as mdifference: + result = other._difference(self.metadata) + + expected = [ + (dummy, self.kwargs[field]) if values[field] == dummy else None + for field in BaseMetadata._fields + ] + self.assertEqual(expected, result) + self.assertEqual(1, mdifference.call_count) + args, kwargs = mdifference.call_args + expected = (self.kwargs["attributes"], values["attributes"]) + self.assertEqual(expected, args) + self.assertEqual(dict(), kwargs) + + +class Test__difference_lenient(tests.IrisTest): + def setUp(self): + self.none = BaseMetadata( + *(None,) * len(BaseMetadata._fields) + )._asdict() + self.names = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + ) + + def test_strict_units(self): + left = self.none.copy() + left["units"] = "km" + right = left.copy() + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + result = lmetadata._difference_lenient(rmetadata) + expected = list(self.none.values()) + self.assertEqual(expected, result) + + def test_strict_units_different(self): + left = self.none.copy() + right = self.none.copy() + lunits, runits = "m", "km" + left["units"] = lunits + right["units"] = runits + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._difference_lenient(rmetadata) + expected = self.none.copy() + expected["units"] = (lunits, runits) + expected = list(expected.values()) + self.assertEqual(expected, result) + + result = rmetadata._difference_lenient(lmetadata) + expected = self.none.copy() + expected["units"] = (runits, lunits) + expected = list(expected.values()) + self.assertEqual(expected, result) + + def test_strict_units_different_none(self): + left = self.none.copy() + right = self.none.copy() + lunits, runits = "m", None + left["units"] = lunits + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._difference_lenient(rmetadata) + expected = self.none.copy() + expected["units"] = (lunits, runits) + expected = list(expected.values()) + + self.assertEqual(expected, result) + result = rmetadata._difference_lenient(lmetadata) + expected = self.none.copy() + expected["units"] = (runits, lunits) + expected = list(expected.values()) + self.assertEqual(expected, result) + + def test_attributes(self): + left = self.none.copy() + right = self.none.copy() + ldict = dict(item=sentinel.left) + rdict = dict(item=sentinel.right) + left["attributes"] = ldict + right["attributes"] = rdict + rmetadata = BaseMetadata(**right) + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, + "_difference_lenient_attributes", + return_value=return_value, + ) as mocker: + lmetadata = BaseMetadata(**left) + result = lmetadata._difference_lenient(rmetadata) + + expected = self.none.copy() + expected["attributes"] = return_value + expected = list(expected.values()) + self.assertEqual(expected, result) + + self.assertEqual(1, mocker.call_count) + args, kwargs = mocker.call_args + expected = (ldict, rdict) + self.assertEqual(expected, args) + self.assertEqual(dict(), kwargs) + + def test_attributes_non_mapping_different(self): + left = self.none.copy() + right = self.none.copy() + ldict = dict(item=sentinel.left) + rdict = sentinel.right + left["attributes"] = ldict + right["attributes"] = rdict + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._difference_lenient(rmetadata) + expected = self.none.copy() + expected["attributes"] = (ldict, rdict) + expected = list(expected.values()) + self.assertEqual(expected, result) + + result = rmetadata._difference_lenient(lmetadata) + expected = self.none.copy() + expected["attributes"] = (rdict, ldict) + expected = list(expected.values()) + self.assertEqual(expected, result) + + def test_attributes_non_mapping_different_none(self): + left = self.none.copy() + right = self.none.copy() + ldict = dict(item=sentinel.left) + left["attributes"] = ldict + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._difference_lenient(rmetadata) + expected = list(self.none.copy().values()) + self.assertEqual(expected, result) + + result = rmetadata._difference_lenient(lmetadata) + self.assertEqual(expected, result) + + def test_names(self): + left = self.none.copy() + left.update(self.names) + right = left.copy() + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._difference_lenient(rmetadata) + expected = list(self.none.values()) + self.assertEqual(expected, result) + + def test_names_different(self): + dummy = sentinel.dummy + left = self.none.copy() + right = self.none.copy() + left.update(self.names) + right["standard_name"] = dummy + right["long_name"] = dummy + right["var_name"] = dummy + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._difference_lenient(rmetadata) + expected = self.none.copy() + expected["standard_name"] = ( + left["standard_name"], + right["standard_name"], + ) + expected["long_name"] = (left["long_name"], right["long_name"]) + expected["var_name"] = (left["var_name"], right["var_name"]) + expected = list(expected.values()) + self.assertEqual(expected, result) + + result = rmetadata._difference_lenient(lmetadata) + expected = self.none.copy() + expected["standard_name"] = ( + right["standard_name"], + left["standard_name"], + ) + expected["long_name"] = (right["long_name"], left["long_name"]) + expected["var_name"] = (right["var_name"], left["var_name"]) + expected = list(expected.values()) + self.assertEqual(expected, result) + + def test_names_different_none(self): + left = self.none.copy() + right = self.none.copy() + left.update(self.names) + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + result = lmetadata._difference_lenient(rmetadata) + expected = list(self.none.values()) + self.assertEqual(expected, result) + + result = rmetadata._difference_lenient(lmetadata) + self.assertEqual(expected, result) + + +class Test__difference_lenient_attributes(tests.IrisTest): + def setUp(self): + self.values = OrderedDict( + one=sentinel.one, + two=sentinel.two, + three=sentinel.three, + four=sentinel.four, + five=sentinel.five, + ) + self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.dummy = sentinel.dummy + + def test_same(self): + left = self.values.copy() + right = self.values.copy() + result = self.metadata._difference_lenient_attributes(left, right) + self.assertIsNone(result) + + def test_different(self): + left = self.values.copy() + right = self.values.copy() + left["two"] = left["four"] = self.dummy + + result = self.metadata._difference_lenient_attributes(left, right) + for key in ["one", "three", "five"]: + del left[key] + del right[key] + expected = (dict(left), dict(right)) + self.assertEqual(expected, result) + + result = self.metadata._difference_lenient_attributes(right, left) + expected = (dict(right), dict(left)) + self.assertEqual(expected, result) + + def test_different_none(self): + left = self.values.copy() + right = self.values.copy() + left["one"] = left["three"] = left["five"] = None + + result = self.metadata._difference_lenient_attributes(left, right) + for key in ["two", "four"]: + del left[key] + del right[key] + expected = (dict(left), dict(right)) + self.assertEqual(expected, result) + + result = self.metadata._difference_lenient_attributes(right, left) + expected = (dict(right), dict(left)) + self.assertEqual(expected, result) + + def test_extra(self): + left = self.values.copy() + right = self.values.copy() + left["extra_left"] = sentinel.extra_left + right["extra_right"] = sentinel.extra_right + result = self.metadata._difference_lenient_attributes(left, right) + expected = self.values.copy() + expected["extra_left"] = left["extra_left"] + expected["extra_right"] = right["extra_right"] + self.assertIsNone(result) + + +class Test__difference_strict_attributes(tests.IrisTest): + def setUp(self): + self.values = OrderedDict( + one=sentinel.one, + two=sentinel.two, + three=sentinel.three, + four=sentinel.four, + five=sentinel.five, + ) + self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.dummy = sentinel.dummy + + def test_same(self): + left = self.values.copy() + right = self.values.copy() + + result = self.metadata._difference_strict_attributes(left, right) + self.assertIsNone(result) + + def test_different(self): + left = self.values.copy() + right = self.values.copy() + left["one"] = left["three"] = left["five"] = self.dummy + + result = self.metadata._difference_strict_attributes(left, right) + expected_left = left.copy() + expected_right = right.copy() + for key in ["two", "four"]: + del expected_left[key] + del expected_right[key] + expected = (expected_left, expected_right) + self.assertEqual(expected, result) + + result = self.metadata._difference_strict_attributes(right, left) + expected_left = left.copy() + expected_right = right.copy() + for key in ["two", "four"]: + del expected_left[key] + del expected_right[key] + expected = (expected_right, expected_left) + self.assertEqual(expected, result) + + def test_different_none(self): + left = self.values.copy() + right = self.values.copy() + left["one"] = left["three"] = left["five"] = None + + result = self.metadata._difference_strict_attributes(left, right) + expected_left = left.copy() + expected_right = right.copy() + for key in ["two", "four"]: + del expected_left[key] + del expected_right[key] + expected = (expected_left, expected_right) + self.assertEqual(expected, result) + + result = self.metadata._difference_strict_attributes(right, left) + expected_left = left.copy() + expected_right = right.copy() + for key in ["two", "four"]: + del expected_left[key] + del expected_right[key] + expected = (expected_right, expected_left) + self.assertEqual(expected, result) + + def test_extra(self): + left = self.values.copy() + right = self.values.copy() + left["extra_left"] = sentinel.extra_left + right["extra_right"] = sentinel.extra_right + + result = self.metadata._difference_strict_attributes(left, right) + expected_left = dict(extra_left=left["extra_left"]) + expected_right = dict(extra_right=right["extra_right"]) + expected = (expected_left, expected_right) + self.assertEqual(expected, result) + + result = self.metadata._difference_strict_attributes(right, left) + expected_left = dict(extra_left=left["extra_left"]) + expected_right = dict(extra_right=right["extra_right"]) + expected = (expected_right, expected_left) + self.assertEqual(expected, result) + + +class Test_difference(tests.IrisTest): + def setUp(self): + kwargs = dict( + standard_name="standard_name", + long_name="long_name", + var_name="var_name", + units="units", + attributes="attributes", + ) + self.metadata = BaseMetadata(**kwargs) + self.mock_kwargs = OrderedDict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + + def test_lenient_service(self): + qualname_difference = qualname(BaseMetadata.difference) + self.assertIn(qualname_difference, LENIENT) + self.assertTrue(LENIENT[qualname_difference]) + + def test_cannot_differ_non_class(self): + emsg = "Cannot differ" + with self.assertRaisesRegex(TypeError, emsg): + self.metadata.difference(None) + + def test_cannot_differ(self): + other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) + emsg = "Cannot differ" + with self.assertRaisesRegex(TypeError, emsg): + self.metadata.difference(other) + + def test_lenient_default(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_difference", return_value=return_value + ) as mocker: + result = self.metadata.difference(self.metadata) + + self.assertEqual(self.mock_kwargs, result._asdict()) + self.assertEqual(1, mocker.call_count) + (args,), kwargs = mocker.call_args + self.assertEqual(id(self.metadata), id(args)) + self.assertEqual(dict(), kwargs) + + def test_lenient_true(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_difference", return_value=return_value + ) as mdifference: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.difference(self.metadata, lenient=True) + + self.assertEqual(mcontext.call_count, 1) + (args,), kwargs = mcontext.call_args + self.assertEqual(args, qualname(BaseMetadata.difference)) + self.assertEqual(kwargs, dict()) + + self.assertEqual(result._asdict(), self.mock_kwargs) + self.assertEqual(mdifference.call_count, 1) + (args,), kwargs = mdifference.call_args self.assertEqual(id(args), id(self.metadata)) self.assertEqual(kwargs, dict()) + def test_lenient_false(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_difference", return_value=return_value + ) as mdifference: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.difference(self.metadata, lenient=False) + + self.assertEqual(mcontext.call_count, 1) + args, kwargs = mcontext.call_args + self.assertEqual((), args) + self.assertEqual({qualname(BaseMetadata.difference): False}, kwargs) + + self.assertEqual(self.mock_kwargs, result._asdict()) + self.assertEqual(1, mdifference.call_count) + (args,), kwargs = mdifference.call_args + self.assertEqual(id(self.metadata), id(args)) + self.assertEqual(dict(), kwargs) + class Test__is_attributes(tests.IrisTest): def setUp(self): @@ -405,29 +934,6 @@ def test_right_not_mapping(self): self.assertFalse(self.metadata._is_attributes(self.field, {}, None)) -class Test___lt__(tests.IrisTest): - def setUp(self): - self.one = BaseMetadata(1, 1, 1, 1, 1) - self.two = BaseMetadata(1, 1, 1, 1, 2) - self.none = BaseMetadata(1, 1, 1, 1, None) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - class Test_name(tests.IrisTest): def setUp(self): self.default = BaseMetadata.DEFAULT_NAME @@ -445,63 +951,72 @@ def _make(standard_name=None, long_name=None, var_name=None): def test_standard_name(self): token = "standard_name" metadata = self._make(standard_name=token) + result = metadata.name() - self.assertEqual(result, token) + self.assertEqual(token, result) result = metadata.name(token=True) - self.assertEqual(result, token) + self.assertEqual(token, result) def test_standard_name__invalid_token(self): token = "nope nope" metadata = self._make(standard_name=token) + result = metadata.name() - self.assertEqual(result, token) + self.assertEqual(token, result) result = metadata.name(token=True) - self.assertEqual(result, self.default) + self.assertEqual(self.default, result) def test_long_name(self): token = "long_name" metadata = self._make(long_name=token) + result = metadata.name() - self.assertEqual(result, token) + self.assertEqual(token, result) result = metadata.name(token=True) - self.assertEqual(result, token) + self.assertEqual(token, result) def test_long_name__invalid_token(self): token = "nope nope" metadata = self._make(long_name=token) + result = metadata.name() - self.assertEqual(result, token) + self.assertEqual(token, result) result = metadata.name(token=True) - self.assertEqual(result, self.default) + self.assertEqual(self.default, result) def test_var_name(self): token = "var_name" metadata = self._make(var_name=token) + result = metadata.name() - self.assertEqual(result, token) + self.assertEqual(token, result) result = metadata.name(token=True) - self.assertEqual(result, token) + self.assertEqual(token, result) def test_var_name__invalid_token(self): token = "nope nope" metadata = self._make(var_name=token) + result = metadata.name() - self.assertEqual(result, token) + self.assertEqual(token, result) result = metadata.name(token=True) - self.assertEqual(result, self.default) + self.assertEqual(self.default, result) def test_default(self): metadata = self._make() + result = metadata.name() - self.assertEqual(result, self.default) + self.assertEqual(self.default, result) result = metadata.name(token=True) - self.assertEqual(result, self.default) + self.assertEqual(self.default, result) def test_default__invalid_token(self): token = "nope nope" metadata = self._make() + result = metadata.name(default=token) - self.assertEqual(result, token) + self.assertEqual(token, result) + emsg = "Cannot retrieve a valid name token" with self.assertRaisesRegex(ValueError, emsg): metadata.name(default=token, token=True) @@ -539,17 +1054,17 @@ def test_fail_colon(self): def test_pass_simple(self): token = "simple" result = BaseMetadata.token(token) - self.assertEqual(result, token) + self.assertEqual(token, result) def test_pass_leading_digit(self): token = "123simple" result = BaseMetadata.token(token) - self.assertEqual(result, token) + self.assertEqual(token, result) def test_pass_mixture(self): token = "S.imple@one+two_3" result = BaseMetadata.token(token) - self.assertEqual(result, token) + self.assertEqual(token, result) if __name__ == "__main__": From 089a02c524d82c9bed355920cc7e14a5d7be20bb Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 18 Jun 2020 13:50:22 +0100 Subject: [PATCH 17/45] added context manager ephemeral comment clarification --- lib/iris/common/lenient.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index afe64edf9d..d9498db09e 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -372,6 +372,10 @@ def context(self, *args, **kwargs): if args: active = self.__dict__["active"] if active is None: + # Ensure not to use "context" as the ephemeral name + # of the context manager runtime "active" lenient client, + # as this causes a namespace clash with this method + # i.e., Lenient.context, via Lenient.__getattr__ active = "_context" self.__dict__["active"] = active self.__dict__[active] = tuple([qualname(arg) for arg in args]) From f65662b8c36def56887f73e6170dfdc6cbf3c5f9 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 18 Jun 2020 14:59:06 +0100 Subject: [PATCH 18/45] add BaseMetadata __ne__ test coverage --- .../unit/common/metadata/test_BaseMetadata.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index b2476239c9..0002482584 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -83,6 +83,49 @@ def test__none_lhs_operand(self): self.assertTrue(result) +class Test___ne__(tests.IrisTest): + def setUp(self): + self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.other = sentinel.other + + def test_notimplemented(self): + return_value = NotImplemented + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = self.metadata.__ne__(self.other) + + self.assertIs(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(self.other, arg) + self.assertEqual(dict(), kwargs) + + def test_negate_true(self): + return_value = True + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = self.metadata.__ne__(self.other) + + self.assertFalse(result) + (arg,), kwargs = mocker.call_args + self.assertEqual(self.other, arg) + self.assertEqual(dict(), kwargs) + + def test_negate_false(self): + return_value = False + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = self.metadata.__ne__(self.other) + + self.assertTrue(result) + (arg,), kwargs = mocker.call_args + self.assertEqual(self.other, arg) + self.assertEqual(dict(), kwargs) + + class Test__combine(tests.IrisTest): def setUp(self): self.kwargs = dict( From fe4cee68f6cf58c515c48b21d29a2975fa4ba026 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 18 Jun 2020 16:18:57 +0100 Subject: [PATCH 19/45] standardise lenient decorator closure names --- lib/iris/common/lenient.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index d9498db09e..a89c4e52e6 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -89,7 +89,7 @@ def func(): (func,) = dargs @wraps(func) - def lenient_inner_naked(*args, **kwargs): + def lenient_client_inner_naked(*args, **kwargs): """ Closure wrapper function to register the wrapped function/method as active at runtime before executing it. @@ -99,7 +99,7 @@ def lenient_inner_naked(*args, **kwargs): result = func(*args, **kwargs) return result - result = lenient_inner_naked + result = lenient_client_inner_naked else: # The decorator has been called with None, zero or more explicit lenient services. if services is None: @@ -108,9 +108,9 @@ def lenient_inner_naked(*args, **kwargs): if isinstance(services, str) or not isinstance(services, Iterable): services = (services,) - def lenient_outer(func): + def lenient_client_outer(func): @wraps(func) - def lenient_inner(*args, **kwargs): + def lenient_client_inner(*args, **kwargs): """ Closure wrapper function to register the wrapped function/method as active at runtime before executing it. @@ -120,9 +120,9 @@ def lenient_inner(*args, **kwargs): result = func(*args, **kwargs) return result - return lenient_inner + return lenient_client_inner - result = lenient_outer + result = lenient_client_outer return result @@ -175,7 +175,7 @@ def func(): LENIENT.register_service(func) @wraps(func) - def register_inner(*args, **kwargs): + def lenient_service_inner_naked(*args, **kwargs): """ Closure wrapper function to execute the lenient service function/method. @@ -183,14 +183,14 @@ def register_inner(*args, **kwargs): """ return func(*args, **kwargs) - result = register_inner + result = lenient_service_inner_naked else: # The decorator has been called with no arguments. - def lenient_outer(func): + def lenient_service_outer(func): LENIENT.register_service(func) @wraps(func) - def lenient_inner(*args, **kwargs): + def lenient_service_inner(*args, **kwargs): """ Closure wrapper function to execute the lenient service function/method. @@ -198,9 +198,9 @@ def lenient_inner(*args, **kwargs): """ return func(*args, **kwargs) - return lenient_inner + return lenient_service_inner - result = lenient_outer + result = lenient_service_outer return result From 85f9c55af7183a0c748236915082ea6760eac033 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 19 Jun 2020 02:54:41 +0100 Subject: [PATCH 20/45] add BaseMetadata equal test coverage --- lib/iris/common/lenient.py | 3 +- lib/iris/common/metadata.py | 53 +- .../unit/common/metadata/test_BaseMetadata.py | 536 ++++++++++++++---- 3 files changed, 483 insertions(+), 109 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index a89c4e52e6..9752121e27 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -6,6 +6,7 @@ from collections.abc import Iterable from contextlib import contextmanager +from copy import deepcopy from functools import wraps from inspect import getmodule import threading @@ -364,7 +365,7 @@ def context(self, *args, **kwargs): """ # Save the original state. - original_state = self.__dict__.copy() + original_state = deepcopy(self.__dict__) # Temporarily update the state with the kwargs first. for name, value in kwargs.items(): self[name] = value diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index b3d4b9af43..05769bbc77 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -111,11 +111,12 @@ def __eq__(self, other): result = NotImplemented if hasattr(other, "__class__") and other.__class__ is self.__class__: if LENIENT(self.__eq__) or LENIENT(self.equal): - # Perform "lenient" comparison. + # Perform "lenient" equality. print("lenient __eq__") result = self._compare_lenient(other) else: - # Perform "strict" comparison. + # Perform "strict" equality. + print("strict __eq__") result = super().__eq__(other) return result @@ -202,9 +203,11 @@ def _api_common( def _combine(self, other): """Perform associated metadata member combination.""" if LENIENT(self.combine): + # Perform "lenient" combine. print("lenient combine") values = self._combine_lenient(other) else: + # Perform "strict" combine. print("strict combine") def func(field): @@ -276,36 +279,62 @@ def _combine_lenient_attributes(left, right): def _compare_lenient(self, other): """ - Perform lenient equality of metadata memberss. + Perform lenient equality of metadata members. Args: - * other (CoordMetadata): - The other coordinate metadata participating in the lenient - comparison. + * other (BaseMetadata): + The other metadata participating in the lenient comparison. Returns: Boolean. """ result = False + # Use the "name" method to leniently compare "standard_name", - # "long_name" and "var_name" in a well defined way. + # "long_name", and "var_name" in a well defined way. if self.name() == other.name(): - # Perform "strict" comparison for "units". - result = self.units == other.units - # Perform "lenient" comparison for "attributes". - # This effectively means they are always equal, therefore there is - # no further work to do here. + + def func(field): + left = getattr(self, field) + right = getattr(other, field) + if field == "units": + # Perform "strict" compare for "units". + result = left == right + elif self._is_attributes(field, left, right): + result = self._compare_lenient_attributes(left, right) + else: + # Perform "lenient" compare for members. + result = (left == right) or left is None or right is None + return result + + result = all([func(field) for field in BaseMetadata._members]) return result + @staticmethod + def _compare_lenient_attributes(left, right): + """Perform lenient compare between the dictionary members.""" + sleft = set(left.items()) + sright = set(right.items()) + # Items in sleft different from sright. + dsleft = dict(sleft - sright) + # Items in sright different from sleft. + dsright = dict(sright - sleft) + # Intersection of common item keys with different values. + keys = set(dsleft.keys()) & set(dsright.keys()) + + return not bool(keys) + def _difference(self, other): """Perform associated metadata member difference.""" if LENIENT(self.difference): + # Perform "lenient" difference. print("lenient difference") values = self._difference_lenient(other) else: + # Perform "strict" difference. print("strict difference") def func(field): diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 0002482584..8cdb37f676 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -13,6 +13,7 @@ import iris.tests as tests from collections import OrderedDict +from copy import deepcopy import unittest.mock as mock from unittest.mock import sentinel @@ -60,6 +61,65 @@ def test__fields(self): self.assertEqual(expected, BaseMetadata._fields) +class Test___eq__(tests.IrisTest): + def setUp(self): + self.kwargs = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + self.metadata = BaseMetadata(**self.kwargs) + + def test_lenient_service(self): + qualname___eq__ = qualname(BaseMetadata.__eq__) + self.assertIn(qualname___eq__, LENIENT) + self.assertTrue(LENIENT[qualname___eq__]) + + def test_cannot_compare_non_class(self): + result = self.metadata.__eq__(None) + self.assertIs(NotImplemented, result) + + def test_cannot_compare(self): + other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) + result = self.metadata.__eq__(other) + self.assertIs(NotImplemented, result) + + def test_lenient(self): + return_value = sentinel.return_value + with mock.patch( + "iris.common.metadata.LENIENT", return_value=True + ) as mlenient: + with mock.patch.object( + BaseMetadata, "_compare_lenient", return_value=return_value + ) as mcompare: + result = self.metadata.__eq__(self.metadata) + + self.assertEqual(return_value, result) + self.assertEqual(1, mcompare.call_count) + (arg,), kwargs = mcompare.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) + + self.assertEqual(1, mlenient.call_count) + (arg,), kwargs = mlenient.call_args + self.assertEqual(qualname(BaseMetadata.__eq__), qualname(arg)) + self.assertEqual(dict(), kwargs) + + def test_strict_same(self): + self.assertTrue(self.metadata.__eq__(self.metadata)) + other = deepcopy(self.metadata) + self.assertTrue(self.metadata.__eq__(other)) + self.assertTrue(other.__eq__(self.metadata)) + + def test_strict_different(self): + self.kwargs["var_name"] = None + other = BaseMetadata(**self.kwargs) + self.assertFalse(self.metadata.__eq__(other)) + self.assertFalse(other.__eq__(self.metadata)) + + class Test___lt__(tests.IrisTest): def setUp(self): self.one = BaseMetadata(1, 1, 1, 1, 1) @@ -149,14 +209,14 @@ def test_lenient(self): result = self.metadata._combine(other) self.assertEqual(1, mlenient.call_count) - (args,), kwargs = mlenient.call_args - self.assertEqual(self.metadata.combine, args) + (arg,), kwargs = mlenient.call_args + self.assertEqual(self.metadata.combine, arg) self.assertEqual(dict(), kwargs) self.assertEqual(return_value, result) self.assertEqual(1, mcombine.call_count) - (args,), kwargs = mcombine.call_args - self.assertEqual(other, args) + (arg,), kwargs = mcombine.call_args + self.assertEqual(other, arg) self.assertEqual(dict(), kwargs) def test_strict(self): @@ -381,90 +441,209 @@ def test_extra(self): self.assertEqual(dict(expected), result) -class Test_combine(tests.IrisTest): +class Test__compare_lenient(tests.IrisTest): def setUp(self): - kwargs = dict( - standard_name="standard_name", - long_name="long_name", - var_name="var_name", - units="units", - attributes="attributes", - ) - self.metadata = BaseMetadata(**kwargs) - self.mock_kwargs = OrderedDict( + self.none = BaseMetadata( + *(None,) * len(BaseMetadata._fields) + )._asdict() + self.names = dict( standard_name=sentinel.standard_name, long_name=sentinel.long_name, var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, ) - def test_lenient_service(self): - qualname_combine = qualname(BaseMetadata.combine) - self.assertIn(qualname_combine, LENIENT) - self.assertTrue(LENIENT[qualname_combine]) + def test_name_same(self): + left = self.none.copy() + left.update(self.names) + right = left.copy() + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) - def test_cannot_combine_non_class(self): - emsg = "Cannot combine" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.combine(None) + with mock.patch.object( + BaseMetadata, "_is_attributes", return_value=False + ) as mocker: + self.assertTrue(lmetadata._compare_lenient(rmetadata)) + self.assertTrue(rmetadata._compare_lenient(lmetadata)) - def test_cannot_combine(self): - other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) - emsg = "Cannot combine" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.combine(other) + expected = (len(BaseMetadata._fields) - 1) * 2 + self.assertEqual(expected, mocker.call_count) + + def test_name_same_lenient_false(self): + left = self.none.copy() + left.update(self.names) + right = self.none.copy() + right["long_name"] = sentinel.standard_name + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) - def test_lenient_default(self): - return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_combine", return_value=return_value + BaseMetadata, "_is_attributes", return_value=False ) as mocker: - result = self.metadata.combine(self.metadata) + self.assertFalse(lmetadata._compare_lenient(rmetadata)) + self.assertFalse(rmetadata._compare_lenient(lmetadata)) - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mocker.call_count) - (args,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(args)) - self.assertEqual(dict(), kwargs) + expected = (len(BaseMetadata._fields) - 1) * 2 + self.assertEqual(expected, mocker.call_count) + + def test_name_different(self): + left = self.none.copy() + left.update(self.names) + right = left.copy() + right["standard_name"] = None + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + with mock.patch.object(BaseMetadata, "_is_attributes") as mocker: + self.assertFalse(lmetadata._compare_lenient(rmetadata)) + self.assertFalse(rmetadata._compare_lenient(lmetadata)) + + self.assertEqual(0, mocker.call_count) + + def test_strict_units(self): + left = self.none.copy() + left.update(self.names) + left["units"] = "K" + right = left.copy() + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) - def test_lenient_true(self): - return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_combine", return_value=return_value - ) as mcombine: - with mock.patch.object(LENIENT, "context") as mcontext: - result = self.metadata.combine(self.metadata, lenient=True) + BaseMetadata, "_is_attributes", return_value=False + ) as mocker: + self.assertTrue(lmetadata._compare_lenient(rmetadata)) + self.assertTrue(rmetadata._compare_lenient(lmetadata)) - self.assertEqual(1, mcontext.call_count) - (args,), kwargs = mcontext.call_args - self.assertEqual(qualname(BaseMetadata.combine), args) - self.assertEqual(dict(), kwargs) + expected = (len(BaseMetadata._fields) - 1) * 2 + self.assertEqual(expected, mocker.call_count) - self.assertEqual(result._asdict(), self.mock_kwargs) - self.assertEqual(1, mcombine.call_count) - (args,), kwargs = mcombine.call_args - self.assertEqual(id(self.metadata), id(args)) - self.assertEqual(dict(), kwargs) + def test_strict_units_different(self): + left = self.none.copy() + left.update(self.names) + left["units"] = "K" + right = left.copy() + right["units"] = "m" + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) - def test_lenient_false(self): - return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_combine", return_value=return_value - ) as mcombine: - with mock.patch.object(LENIENT, "context") as mcontext: - result = self.metadata.combine(self.metadata, lenient=False) + BaseMetadata, "_is_attributes", return_value=False + ) as mocker: + self.assertFalse(lmetadata._compare_lenient(rmetadata)) + self.assertFalse(rmetadata._compare_lenient(lmetadata)) - self.assertEqual(1, mcontext.call_count) - args, kwargs = mcontext.call_args - self.assertEqual((), args) - self.assertEqual({qualname(BaseMetadata.combine): False}, kwargs) + expected = (len(BaseMetadata._fields) - 1) * 2 + self.assertEqual(expected, mocker.call_count) - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mcombine.call_count) - (args,), kwargs = mcombine.call_args - self.assertEqual(id(self.metadata), id(args)) - self.assertEqual(dict(), kwargs) + def test_attributes(self): + left = self.none.copy() + left.update(self.names) + right = left.copy() + ldict = dict(item=sentinel.left) + rdict = dict(item=sentinel.right) + left["attributes"] = ldict + right["attributes"] = rdict + rmetadata = BaseMetadata(**right) + with mock.patch.object( + BaseMetadata, "_compare_lenient_attributes", return_value=True, + ) as mocker: + lmetadata = BaseMetadata(**left) + self.assertTrue(lmetadata._compare_lenient(rmetadata)) + self.assertTrue(rmetadata._compare_lenient(lmetadata)) + + self.assertEqual(2, mocker.call_count) + expected = [((ldict, rdict),), ((rdict, ldict),)] + self.assertEqual(expected, mocker.call_args_list) + + def test_attributes_non_mapping_different(self): + left = self.none.copy() + left.update(self.names) + right = left.copy() + ldict = dict(item=sentinel.left) + rdict = sentinel.right + left["attributes"] = ldict + right["attributes"] = rdict + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + self.assertFalse(lmetadata._compare_lenient(rmetadata)) + self.assertFalse(rmetadata._compare_lenient(lmetadata)) + + def test_attributes_non_mapping_different_none(self): + left = self.none.copy() + left.update(self.names) + right = left.copy() + ldict = dict(item=sentinel.left) + left["attributes"] = ldict + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + self.assertTrue(lmetadata._compare_lenient(rmetadata)) + self.assertTrue(rmetadata._combine_lenient(lmetadata)) + + def test_names(self): + left = self.none.copy() + left.update(self.names) + left["long_name"] = None + right = self.none.copy() + right["long_name"] = left["standard_name"] + lmetadata = BaseMetadata(**left) + rmetadata = BaseMetadata(**right) + + self.assertTrue(lmetadata._compare_lenient(rmetadata)) + self.assertTrue(rmetadata._combine_lenient(lmetadata)) + + +class Test__compare_lenient_attributes(tests.IrisTest): + def setUp(self): + self.values = OrderedDict( + one=sentinel.one, + two=sentinel.two, + three=sentinel.three, + four=sentinel.four, + five=sentinel.five, + ) + self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.dummy = sentinel.dummy + + def test_same(self): + left = self.values.copy() + right = self.values.copy() + + self.assertTrue(self.metadata._compare_lenient_attributes(left, right)) + self.assertTrue(self.metadata._compare_lenient_attributes(right, left)) + + def test_different(self): + left = self.values.copy() + right = self.values.copy() + left["two"] = left["four"] = self.dummy + + self.assertFalse( + self.metadata._compare_lenient_attributes(left, right) + ) + self.assertFalse( + self.metadata._compare_lenient_attributes(right, left) + ) + + def test_different_none(self): + left = self.values.copy() + right = self.values.copy() + left["one"] = left["three"] = left["five"] = None + + self.assertFalse( + self.metadata._compare_lenient_attributes(left, right) + ) + self.assertFalse( + self.metadata._compare_lenient_attributes(right, left) + ) + + def test_extra(self): + left = self.values.copy() + right = self.values.copy() + left["extra_left"] = sentinel.extra_left + right["extra_right"] = sentinel.extra_right + + self.assertTrue(self.metadata._compare_lenient_attributes(left, right)) + self.assertTrue(self.metadata._compare_lenient_attributes(right, left)) class Test__difference(tests.IrisTest): @@ -490,14 +669,14 @@ def test_lenient(self): result = self.metadata._difference(other) self.assertEqual(1, mlenient.call_count) - (args,), kwargs = mlenient.call_args - self.assertEqual(self.metadata.difference, args) + (arg,), kwargs = mlenient.call_args + self.assertEqual(self.metadata.difference, arg) self.assertEqual(dict(), kwargs) self.assertEqual(return_value, result) self.assertEqual(1, mdifference.call_count) - (args,), kwargs = mdifference.call_args - self.assertEqual(other, args) + (arg,), kwargs = mdifference.call_args + self.assertEqual(other, arg) self.assertEqual(dict(), kwargs) def test_strict(self): @@ -873,6 +1052,110 @@ def test_extra(self): self.assertEqual(expected, result) +class Test__is_attributes(tests.IrisTest): + def setUp(self): + self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.field = "attributes" + + def test_field(self): + self.assertTrue(self.metadata._is_attributes(self.field, {}, {})) + + def test_field_not_attributes(self): + self.assertFalse(self.metadata._is_attributes(None, {}, {})) + + def test_left_not_mapping(self): + self.assertFalse(self.metadata._is_attributes(self.field, None, {})) + + def test_right_not_mapping(self): + self.assertFalse(self.metadata._is_attributes(self.field, {}, None)) + + +class Test_combine(tests.IrisTest): + def setUp(self): + kwargs = dict( + standard_name="standard_name", + long_name="long_name", + var_name="var_name", + units="units", + attributes="attributes", + ) + self.metadata = BaseMetadata(**kwargs) + self.mock_kwargs = OrderedDict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + + def test_lenient_service(self): + qualname_combine = qualname(BaseMetadata.combine) + self.assertIn(qualname_combine, LENIENT) + self.assertTrue(LENIENT[qualname_combine]) + + def test_cannot_combine_non_class(self): + emsg = "Cannot combine" + with self.assertRaisesRegex(TypeError, emsg): + self.metadata.combine(None) + + def test_cannot_combine(self): + other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) + emsg = "Cannot combine" + with self.assertRaisesRegex(TypeError, emsg): + self.metadata.combine(other) + + def test_lenient_default(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_combine", return_value=return_value + ) as mocker: + result = self.metadata.combine(self.metadata) + + self.assertEqual(self.mock_kwargs, result._asdict()) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) + + def test_lenient_true(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_combine", return_value=return_value + ) as mcombine: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.combine(self.metadata, lenient=True) + + self.assertEqual(1, mcontext.call_count) + (arg,), kwargs = mcontext.call_args + self.assertEqual(qualname(BaseMetadata.combine), arg) + self.assertEqual(dict(), kwargs) + + self.assertEqual(result._asdict(), self.mock_kwargs) + self.assertEqual(1, mcombine.call_count) + (arg,), kwargs = mcombine.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) + + def test_lenient_false(self): + return_value = self.mock_kwargs.values() + with mock.patch.object( + BaseMetadata, "_combine", return_value=return_value + ) as mcombine: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.combine(self.metadata, lenient=False) + + self.assertEqual(1, mcontext.call_count) + args, kwargs = mcontext.call_args + self.assertEqual((), args) + self.assertEqual({qualname(BaseMetadata.combine): False}, kwargs) + + self.assertEqual(self.mock_kwargs, result._asdict()) + self.assertEqual(1, mcombine.call_count) + (arg,), kwargs = mcombine.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) + + class Test_difference(tests.IrisTest): def setUp(self): kwargs = dict( @@ -916,8 +1199,8 @@ def test_lenient_default(self): self.assertEqual(self.mock_kwargs, result._asdict()) self.assertEqual(1, mocker.call_count) - (args,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(args)) + (arg,), kwargs = mocker.call_args + self.assertEqual(id(self.metadata), id(arg)) self.assertEqual(dict(), kwargs) def test_lenient_true(self): @@ -928,16 +1211,16 @@ def test_lenient_true(self): with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.difference(self.metadata, lenient=True) - self.assertEqual(mcontext.call_count, 1) - (args,), kwargs = mcontext.call_args - self.assertEqual(args, qualname(BaseMetadata.difference)) - self.assertEqual(kwargs, dict()) + self.assertEqual(1, mcontext.call_count) + (arg,), kwargs = mcontext.call_args + self.assertEqual(qualname(BaseMetadata.difference), arg) + self.assertEqual(dict(), kwargs) - self.assertEqual(result._asdict(), self.mock_kwargs) - self.assertEqual(mdifference.call_count, 1) - (args,), kwargs = mdifference.call_args - self.assertEqual(id(args), id(self.metadata)) - self.assertEqual(kwargs, dict()) + self.assertEqual(self.mock_kwargs, result._asdict()) + self.assertEqual(1, mdifference.call_count) + (arg,), kwargs = mdifference.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) def test_lenient_false(self): return_value = self.mock_kwargs.values() @@ -954,27 +1237,88 @@ def test_lenient_false(self): self.assertEqual(self.mock_kwargs, result._asdict()) self.assertEqual(1, mdifference.call_count) - (args,), kwargs = mdifference.call_args - self.assertEqual(id(self.metadata), id(args)) + (arg,), kwargs = mdifference.call_args + self.assertEqual(id(self.metadata), id(arg)) self.assertEqual(dict(), kwargs) -class Test__is_attributes(tests.IrisTest): +class Test_equal(tests.IrisTest): def setUp(self): - self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) - self.field = "attributes" + kwargs = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + self.metadata = BaseMetadata(**kwargs) - def test_field(self): - self.assertTrue(self.metadata._is_attributes(self.field, {}, {})) + def test_lenient_service(self): + qualname_equal = qualname(BaseMetadata.equal) + self.assertIn(qualname_equal, LENIENT) + self.assertTrue(LENIENT[qualname_equal]) - def test_field_not_attributes(self): - self.assertFalse(self.metadata._is_attributes(None, {}, {})) + def test_cannot_compare_non_class(self): + emsg = "Cannot compare" + with self.assertRaisesRegex(TypeError, emsg): + self.metadata.equal(None) - def test_left_not_mapping(self): - self.assertFalse(self.metadata._is_attributes(self.field, None, {})) + def test_cannot_compare(self): + other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) + emsg = "Cannot compare" + with self.assertRaisesRegex(TypeError, emsg): + self.metadata.equal(other) - def test_right_not_mapping(self): - self.assertFalse(self.metadata._is_attributes(self.field, {}, None)) + def test_lenient_default(self): + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = self.metadata.equal(self.metadata) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) + + def test_lenient_true(self): + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as m__eq__: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.equal(self.metadata, lenient=True) + + self.assertEqual(return_value, result) + self.assertEqual(1, mcontext.call_count) + (arg,), kwargs = mcontext.call_args + self.assertEqual(qualname(BaseMetadata.equal), arg) + self.assertEqual(dict(), kwargs) + + self.assertEqual(1, m__eq__.call_count) + (arg,), kwargs = m__eq__.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) + + def test_lenient_false(self): + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as m__eq__: + with mock.patch.object(LENIENT, "context") as mcontext: + result = self.metadata.equal(self.metadata, lenient=False) + + self.assertEqual(1, mcontext.call_count) + args, kwargs = mcontext.call_args + self.assertEqual((), args) + self.assertEqual({qualname(BaseMetadata.equal): False}, kwargs) + + self.assertEqual(return_value, result) + self.assertEqual(1, m__eq__.call_count) + (arg,), kwargs = m__eq__.call_args + self.assertEqual(id(self.metadata), id(arg)) + self.assertEqual(dict(), kwargs) class Test_name(tests.IrisTest): From edcc20d96e48071e40f6919e8629cf3cf2a707ee Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 19 Jun 2020 10:03:15 +0100 Subject: [PATCH 21/45] half dunder context --- lib/iris/common/lenient.py | 2 +- lib/iris/tests/unit/common/lenient/test_Lenient.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 9752121e27..0e2de17b82 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -377,7 +377,7 @@ def context(self, *args, **kwargs): # of the context manager runtime "active" lenient client, # as this causes a namespace clash with this method # i.e., Lenient.context, via Lenient.__getattr__ - active = "_context" + active = "__context" self.__dict__["active"] = active self.__dict__[active] = tuple([qualname(arg) for arg in args]) try: diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index 5d6eedcde8..be881c7115 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -579,7 +579,7 @@ def test_context_runtime(self): post = self.copy() self.assertEqual(pre, self.default) expected = self.default.copy() - expected.update(dict(active="_context", _context=services)) + expected.update(dict(active="__context", __context=services)) self.assertEqual(context, expected) self.assertEqual(post, self.default) From 8a218c75384f1712d984644d6f518652f5343c9b Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 19 Jun 2020 12:08:04 +0100 Subject: [PATCH 22/45] add AncillaryVariableMetadata test coverage --- lib/iris/common/metadata.py | 1 - .../test_AncillaryVariableMetadata.py | 179 ++++++++++++++++++ .../unit/common/metadata/test_BaseMetadata.py | 4 + 3 files changed, 183 insertions(+), 1 deletion(-) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 05769bbc77..f87f3ccc15 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -116,7 +116,6 @@ def __eq__(self, other): result = self._compare_lenient(other) else: # Perform "strict" equality. - print("strict __eq__") result = super().__eq__(other) return result diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index 0fa0cd56bf..af4dea8dfc 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -13,7 +13,9 @@ import iris.tests as tests import unittest.mock as mock +from unittest.mock import sentinel +from iris.common.lenient import LENIENT, qualname from iris.common.metadata import BaseMetadata, AncillaryVariableMetadata @@ -60,5 +62,182 @@ def test_bases(self): self.assertTrue(issubclass(AncillaryVariableMetadata, BaseMetadata)) +class Test___eq__(tests.IrisTest): + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.__eq__.__doc__, + AncillaryVariableMetadata.__eq__.__doc__, + ) + + def test_lenient_service(self): + qualname___eq__ = qualname(AncillaryVariableMetadata.__eq__) + self.assertIn(qualname___eq__, LENIENT) + self.assertTrue(LENIENT[qualname___eq__]) + self.assertTrue(LENIENT[AncillaryVariableMetadata.__eq__]) + + def test(self): + other = sentinel.other + return_value = sentinel.return_value + metadata = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = metadata.__eq__(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(), kwargs) + + +class Test_combine(tests.IrisTest): + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.combine.__doc__, + AncillaryVariableMetadata.combine.__doc__, + ) + + def test_lenient_service(self): + qualname_combine = qualname(AncillaryVariableMetadata.combine) + self.assertIn(qualname_combine, LENIENT) + self.assertTrue(LENIENT[qualname_combine]) + self.assertTrue(LENIENT[AncillaryVariableMetadata.combine]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + metadata = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = metadata.combine(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + metadata = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = metadata.combine(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + +class Test_difference(tests.IrisTest): + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.difference.__doc__, + AncillaryVariableMetadata.difference.__doc__, + ) + + def test_lenient_service(self): + qualname_difference = qualname(AncillaryVariableMetadata.difference) + self.assertIn(qualname_difference, LENIENT) + self.assertTrue(LENIENT[qualname_difference]) + self.assertTrue(LENIENT[AncillaryVariableMetadata.difference]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + metadata = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = metadata.difference(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + metadata = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = metadata.difference(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + +class Test_equal(tests.IrisTest): + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.equal.__doc__, AncillaryVariableMetadata.equal.__doc__ + ) + + def test_lenient_service(self): + qualname_equal = qualname(AncillaryVariableMetadata.equal) + self.assertIn(qualname_equal, LENIENT) + self.assertTrue(LENIENT[qualname_equal]) + self.assertTrue(LENIENT[AncillaryVariableMetadata.equal]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + metadata = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = metadata.equal(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + metadata = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = metadata.equal(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 8cdb37f676..074f96ce13 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -76,6 +76,7 @@ def test_lenient_service(self): qualname___eq__ = qualname(BaseMetadata.__eq__) self.assertIn(qualname___eq__, LENIENT) self.assertTrue(LENIENT[qualname___eq__]) + self.assertTrue(LENIENT[BaseMetadata.__eq__]) def test_cannot_compare_non_class(self): result = self.metadata.__eq__(None) @@ -1092,6 +1093,7 @@ def test_lenient_service(self): qualname_combine = qualname(BaseMetadata.combine) self.assertIn(qualname_combine, LENIENT) self.assertTrue(LENIENT[qualname_combine]) + self.assertTrue(LENIENT[BaseMetadata.combine]) def test_cannot_combine_non_class(self): emsg = "Cannot combine" @@ -1178,6 +1180,7 @@ def test_lenient_service(self): qualname_difference = qualname(BaseMetadata.difference) self.assertIn(qualname_difference, LENIENT) self.assertTrue(LENIENT[qualname_difference]) + self.assertTrue(LENIENT[BaseMetadata.difference]) def test_cannot_differ_non_class(self): emsg = "Cannot differ" @@ -1257,6 +1260,7 @@ def test_lenient_service(self): qualname_equal = qualname(BaseMetadata.equal) self.assertIn(qualname_equal, LENIENT) self.assertTrue(LENIENT[qualname_equal]) + self.assertTrue((LENIENT[BaseMetadata.equal])) def test_cannot_compare_non_class(self): emsg = "Cannot compare" From 81fec8ed204067c355a9959d8ee617d3e40a1990 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 19 Jun 2020 15:19:02 +0100 Subject: [PATCH 23/45] add additional AncillaryVariableMetadata test coverage --- .../test_AncillaryVariableMetadata.py | 254 +++++++++++++++++- 1 file changed, 253 insertions(+), 1 deletion(-) diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index af4dea8dfc..045e7ab9f9 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -63,6 +63,16 @@ def test_bases(self): class Test___eq__(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + self.dummy = sentinel.dummy + def test_wraps_docstring(self): self.assertEqual( BaseMetadata.__eq__.__doc__, @@ -75,7 +85,7 @@ def test_lenient_service(self): self.assertTrue(LENIENT[qualname___eq__]) self.assertTrue(LENIENT[AncillaryVariableMetadata.__eq__]) - def test(self): + def test_call(self): other = sentinel.other return_value = sentinel.return_value metadata = AncillaryVariableMetadata( @@ -92,8 +102,74 @@ def test(self): self.assertEqual(other, arg) self.assertEqual(dict(), kwargs) + def test_op_lenient_same(self): + lmetadata = AncillaryVariableMetadata(**self.values) + rmetadata = AncillaryVariableMetadata(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_same_none(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = AncillaryVariableMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_different(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = AncillaryVariableMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_same(self): + lmetadata = AncillaryVariableMetadata(**self.values) + rmetadata = AncillaryVariableMetadata(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = AncillaryVariableMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_none(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = AncillaryVariableMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + class Test_combine(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + self.dummy = sentinel.dummy + def test_wraps_docstring(self): self.assertEqual( BaseMetadata.combine.__doc__, @@ -141,8 +217,86 @@ def test_lenient(self): self.assertEqual(other, arg) self.assertEqual(dict(lenient=lenient), kwargs) + def test_op_lenient_same(self): + lmetadata = AncillaryVariableMetadata(**self.values) + rmetadata = AncillaryVariableMetadata(**self.values) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_same_none(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = AncillaryVariableMetadata(**right) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_different(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = AncillaryVariableMetadata(**right) + expected = self.values.copy() + expected["units"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_same(self): + lmetadata = AncillaryVariableMetadata(**self.values) + rmetadata = AncillaryVariableMetadata(**self.values) + expected = self.values.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = AncillaryVariableMetadata(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_none(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = AncillaryVariableMetadata(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + class Test_difference(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + ) + self.dummy = sentinel.dummy + self.none = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + )._asdict() + def test_wraps_docstring(self): self.assertEqual( BaseMetadata.difference.__doc__, @@ -190,6 +344,104 @@ def test_lenient(self): self.assertEqual(other, arg) self.assertEqual(dict(lenient=lenient), kwargs) + def test_op_lenient_same(self): + lmetadata = AncillaryVariableMetadata(**self.values) + rmetadata = AncillaryVariableMetadata(**self.values) + expected = self.none.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_same_none(self): + lmetadata = AncillaryVariableMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = AncillaryVariableMetadata(**right) + expected = self.none.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_different(self): + left = self.values.copy() + lmetadata = AncillaryVariableMetadata(**left) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = AncillaryVariableMetadata(**right) + lexpected = self.none.copy() + lexpected["units"] = (left["units"], right["units"]) + rexpected = self.none.copy() + rexpected["units"] = lexpected["units"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_same(self): + lmetadata = AncillaryVariableMetadata(**self.values) + rmetadata = AncillaryVariableMetadata(**self.values) + expected = self.none.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different(self): + left = self.values.copy() + lmetadata = AncillaryVariableMetadata(**left) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = AncillaryVariableMetadata(**right) + lexpected = self.none.copy() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = self.none.copy() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_none(self): + left = self.values.copy() + lmetadata = AncillaryVariableMetadata(**left) + right = self.values.copy() + right["long_name"] = None + rmetadata = AncillaryVariableMetadata(**right) + lexpected = self.none.copy() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = self.none.copy() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + class Test_equal(tests.IrisTest): def test_wraps_docstring(self): From 06fbb7226c66c2625100721e1d5e56e9ca4cbecf Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 22 Jun 2020 09:22:06 +0100 Subject: [PATCH 24/45] add CellMeasureMetadata test coverage --- lib/iris/common/metadata.py | 2 + .../test_AncillaryVariableMetadata.py | 59 +- .../metadata/test_CellMeasureMetadata.py | 585 ++++++++++++++++++ 3 files changed, 612 insertions(+), 34 deletions(-) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index f87f3ccc15..c6778591d1 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -308,6 +308,7 @@ def func(field): result = (left == right) or left is None or right is None return result + # Note that, we use "_members" not "_fields". result = all([func(field) for field in BaseMetadata._members]) return result @@ -381,6 +382,7 @@ def func(field): ) return result + # Note that, we use "_members" not "_fields". return [func(field) for field in BaseMetadata._members] @staticmethod diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index 045e7ab9f9..cb91e83428 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -12,6 +12,7 @@ # importing anything else. import iris.tests as tests +from copy import deepcopy import unittest.mock as mock from unittest.mock import sentinel @@ -169,6 +170,9 @@ def setUp(self): attributes=sentinel.attributes, ) self.dummy = sentinel.dummy + self.none = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) def test_wraps_docstring(self): self.assertEqual( @@ -185,13 +189,10 @@ def test_lenient_service(self): def test_lenient_default(self): other = sentinel.other return_value = sentinel.return_value - metadata = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) with mock.patch.object( BaseMetadata, "combine", return_value=return_value ) as mocker: - result = metadata.combine(other) + result = self.none.combine(other) self.assertEqual(return_value, result) self.assertEqual(1, mocker.call_count) @@ -203,13 +204,10 @@ def test_lenient(self): other = sentinel.other lenient = sentinel.lenient return_value = sentinel.return_value - metadata = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) with mock.patch.object( BaseMetadata, "combine", return_value=return_value ) as mocker: - result = metadata.combine(other, lenient=lenient) + result = self.none.combine(other, lenient=lenient) self.assertEqual(return_value, result) self.assertEqual(1, mocker.call_count) @@ -295,7 +293,7 @@ def setUp(self): self.dummy = sentinel.dummy self.none = AncillaryVariableMetadata( *(None,) * len(AncillaryVariableMetadata._fields) - )._asdict() + ) def test_wraps_docstring(self): self.assertEqual( @@ -312,13 +310,10 @@ def test_lenient_service(self): def test_lenient_default(self): other = sentinel.other return_value = sentinel.return_value - metadata = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) with mock.patch.object( BaseMetadata, "difference", return_value=return_value ) as mocker: - result = metadata.difference(other) + result = self.none.difference(other) self.assertEqual(return_value, result) self.assertEqual(1, mocker.call_count) @@ -330,13 +325,10 @@ def test_lenient(self): other = sentinel.other lenient = sentinel.lenient return_value = sentinel.return_value - metadata = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) with mock.patch.object( BaseMetadata, "difference", return_value=return_value ) as mocker: - result = metadata.difference(other, lenient=lenient) + result = self.none.difference(other, lenient=lenient) self.assertEqual(return_value, result) self.assertEqual(1, mocker.call_count) @@ -347,7 +339,7 @@ def test_lenient(self): def test_op_lenient_same(self): lmetadata = AncillaryVariableMetadata(**self.values) rmetadata = AncillaryVariableMetadata(**self.values) - expected = self.none.copy() + expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertEqual( @@ -362,7 +354,7 @@ def test_op_lenient_same_none(self): right = self.values.copy() right["var_name"] = None rmetadata = AncillaryVariableMetadata(**right) - expected = self.none.copy() + expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertEqual( @@ -378,9 +370,9 @@ def test_op_lenient_different(self): right = self.values.copy() right["units"] = self.dummy rmetadata = AncillaryVariableMetadata(**right) - lexpected = self.none.copy() + lexpected = deepcopy(self.none)._asdict() lexpected["units"] = (left["units"], right["units"]) - rexpected = self.none.copy() + rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -394,7 +386,7 @@ def test_op_lenient_different(self): def test_op_strict_same(self): lmetadata = AncillaryVariableMetadata(**self.values) rmetadata = AncillaryVariableMetadata(**self.values) - expected = self.none.copy() + expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertEqual( @@ -410,9 +402,9 @@ def test_op_strict_different(self): right = self.values.copy() right["long_name"] = self.dummy rmetadata = AncillaryVariableMetadata(**right) - lexpected = self.none.copy() + lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = self.none.copy() + rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -429,9 +421,9 @@ def test_op_strict_different_none(self): right = self.values.copy() right["long_name"] = None rmetadata = AncillaryVariableMetadata(**right) - lexpected = self.none.copy() + lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = self.none.copy() + rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -444,6 +436,11 @@ def test_op_strict_different_none(self): class Test_equal(tests.IrisTest): + def setUp(self): + self.none = AncillaryVariableMetadata( + *(None,) * len(AncillaryVariableMetadata._fields) + ) + def test_wraps_docstring(self): self.assertEqual( BaseMetadata.equal.__doc__, AncillaryVariableMetadata.equal.__doc__ @@ -458,13 +455,10 @@ def test_lenient_service(self): def test_lenient_default(self): other = sentinel.other return_value = sentinel.return_value - metadata = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) with mock.patch.object( BaseMetadata, "equal", return_value=return_value ) as mocker: - result = metadata.equal(other) + result = self.none.equal(other) self.assertEqual(return_value, result) self.assertEqual(1, mocker.call_count) @@ -476,13 +470,10 @@ def test_lenient(self): other = sentinel.other lenient = sentinel.lenient return_value = sentinel.return_value - metadata = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) with mock.patch.object( BaseMetadata, "equal", return_value=return_value ) as mocker: - result = metadata.equal(other, lenient=lenient) + result = self.none.equal(other, lenient=lenient) self.assertEqual(return_value, result) self.assertEqual(1, mocker.call_count) diff --git a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py index 6a2ffbd70c..63a5bb99d3 100644 --- a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py @@ -12,8 +12,11 @@ # importing anything else. import iris.tests as tests +from copy import deepcopy import unittest.mock as mock +from unittest.mock import sentinel +from iris.common.lenient import LENIENT, qualname from iris.common.metadata import BaseMetadata, CellMeasureMetadata @@ -64,5 +67,587 @@ def test_bases(self): self.assertTrue(issubclass(CellMeasureMetadata, BaseMetadata)) +class Test___eq__(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + measure=sentinel.measure, + ) + self.dummy = sentinel.dummy + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.__eq__.__doc__, CellMeasureMetadata.__eq__.__doc__, + ) + + def test_lenient_service(self): + qualname___eq__ = qualname(CellMeasureMetadata.__eq__) + self.assertIn(qualname___eq__, LENIENT) + self.assertTrue(LENIENT[qualname___eq__]) + self.assertTrue(LENIENT[CellMeasureMetadata.__eq__]) + + def test_call(self): + other = sentinel.other + return_value = sentinel.return_value + metadata = CellMeasureMetadata( + *(None,) * len(CellMeasureMetadata._fields) + ) + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = metadata.__eq__(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(), kwargs) + + def test_op_lenient_same(self): + lmetadata = CellMeasureMetadata(**self.values) + rmetadata = CellMeasureMetadata(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_same_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_same_measure_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = None + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_different(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_different_measure(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_same(self): + lmetadata = CellMeasureMetadata(**self.values) + rmetadata = CellMeasureMetadata(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_measure(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_measure_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = None + rmetadata = CellMeasureMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + +class Test_combine(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + measure=sentinel.measure, + ) + self.dummy = sentinel.dummy + self.none = CellMeasureMetadata( + *(None,) * len(CellMeasureMetadata._fields) + ) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.combine.__doc__, CellMeasureMetadata.combine.__doc__, + ) + + def test_lenient_service(self): + qualname_combine = qualname(CellMeasureMetadata.combine) + self.assertIn(qualname_combine, LENIENT) + self.assertTrue(LENIENT[qualname_combine]) + self.assertTrue(LENIENT[CellMeasureMetadata.combine]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = self.none.combine(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = self.none.combine(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + def test_op_lenient_same(self): + lmetadata = CellMeasureMetadata(**self.values) + rmetadata = CellMeasureMetadata(**self.values) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_same_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = CellMeasureMetadata(**right) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_same_measure_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = None + rmetadata = CellMeasureMetadata(**right) + expected = right.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertTrue(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_different(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + expected = self.values.copy() + expected["units"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_different_measure(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + expected = self.values.copy() + expected["measure"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_same(self): + lmetadata = CellMeasureMetadata(**self.values) + rmetadata = CellMeasureMetadata(**self.values) + expected = self.values.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_measure(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + expected = self.values.copy() + expected["measure"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = CellMeasureMetadata(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_measure_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = None + rmetadata = CellMeasureMetadata(**right) + expected = self.values.copy() + expected["measure"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + +class Test_difference(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + measure=sentinel.measure, + ) + self.dummy = sentinel.dummy + self.none = CellMeasureMetadata( + *(None,) * len(CellMeasureMetadata._fields) + ) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.difference.__doc__, + CellMeasureMetadata.difference.__doc__, + ) + + def test_lenient_service(self): + qualname_difference = qualname(CellMeasureMetadata.difference) + self.assertIn(qualname_difference, LENIENT) + self.assertTrue(LENIENT[qualname_difference]) + self.assertTrue(LENIENT[CellMeasureMetadata.difference]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = self.none.difference(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = self.none.difference(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + def test_op_lenient_same(self): + lmetadata = CellMeasureMetadata(**self.values) + rmetadata = CellMeasureMetadata(**self.values) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_same_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = CellMeasureMetadata(**right) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_same_measure_none(self): + lmetadata = CellMeasureMetadata(**self.values) + right = self.values.copy() + right["measure"] = None + rmetadata = CellMeasureMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["measure"] = (sentinel.measure, None) + rexpected = deepcopy(self.none)._asdict() + rexpected["measure"] = (None, sentinel.measure) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_different(self): + left = self.values.copy() + lmetadata = CellMeasureMetadata(**left) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["units"] = (left["units"], right["units"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["units"] = lexpected["units"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_different_measure(self): + left = self.values.copy() + lmetadata = CellMeasureMetadata(**left) + right = self.values.copy() + right["measure"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["measure"] = (left["measure"], right["measure"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["measure"] = lexpected["measure"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_same(self): + lmetadata = CellMeasureMetadata(**self.values) + rmetadata = CellMeasureMetadata(**self.values) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different(self): + left = self.values.copy() + lmetadata = CellMeasureMetadata(**left) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_measure(self): + left = self.values.copy() + lmetadata = CellMeasureMetadata(**left) + right = self.values.copy() + right["measure"] = self.dummy + rmetadata = CellMeasureMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["measure"] = (left["measure"], right["measure"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["measure"] = lexpected["measure"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_none(self): + left = self.values.copy() + lmetadata = CellMeasureMetadata(**left) + right = self.values.copy() + right["long_name"] = None + rmetadata = CellMeasureMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_measure_none(self): + left = self.values.copy() + lmetadata = CellMeasureMetadata(**left) + right = self.values.copy() + right["measure"] = None + rmetadata = CellMeasureMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["measure"] = (left["measure"], right["measure"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["measure"] = lexpected["measure"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + +class Test_equal(tests.IrisTest): + def setUp(self): + self.none = CellMeasureMetadata( + *(None,) * len(CellMeasureMetadata._fields) + ) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.equal.__doc__, CellMeasureMetadata.equal.__doc__ + ) + + def test_lenient_service(self): + qualname_equal = qualname(CellMeasureMetadata.equal) + self.assertIn(qualname_equal, LENIENT) + self.assertTrue(LENIENT[qualname_equal]) + self.assertTrue(LENIENT[CellMeasureMetadata.equal]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = self.none.equal(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = self.none.equal(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + if __name__ == "__main__": tests.main() From 22e011565c50de8bc6983b2370d7ff6709e22d7a Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Mon, 22 Jun 2020 12:15:14 +0100 Subject: [PATCH 25/45] Clarify lenient_service operation + simplify code. --- lib/iris/common/lenient.py | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 0e2de17b82..9776ef318f 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -171,35 +171,24 @@ def func(): if ndargs: # The decorator has been used as a simple naked decorator. + # Thus the (single) argument is a function to be wrapped. + # We just register the argument function as a lenient service, and + # return it unchanged (func,) = dargs LENIENT.register_service(func) - @wraps(func) - def lenient_service_inner_naked(*args, **kwargs): - """ - Closure wrapper function to execute the lenient service - function/method. + # This decorator registers 'func': the func itself is unchanged. + result = func - """ - return func(*args, **kwargs) - - result = lenient_service_inner_naked else: # The decorator has been called with no arguments. + # Return a decorator, to apply to 'func' immediately following. def lenient_service_outer(func): LENIENT.register_service(func) - @wraps(func) - def lenient_service_inner(*args, **kwargs): - """ - Closure wrapper function to execute the lenient service - function/method. - - """ - return func(*args, **kwargs) - - return lenient_service_inner + # Decorator registers 'func', but func itself is unchanged. + return func result = lenient_service_outer From 679433d35e90df4b2efea8d18e4748a2f5f869b4 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 22 Jun 2020 13:54:14 +0100 Subject: [PATCH 26/45] add CoordMetadata test coverage --- lib/iris/common/metadata.py | 7 +- .../common/metadata/test_CoordMetadata.py | 620 ++++++++++++++++++ 2 files changed, 625 insertions(+), 2 deletions(-) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index c6778591d1..b00850f670 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -739,9 +739,11 @@ def _combine_lenient(self, other): """ # Perform "strict" combination for "coord_system" and "climatological". def func(field): - value = getattr(self, field) - return value if value == getattr(other, field) else None + left = getattr(self, field) + right = getattr(other, field) + return left if left == right else None + # Note that, we use "_members" not "_fields". values = [func(field) for field in self._members] # Perform lenient combination of the other parent members. result = super()._combine_lenient(other) @@ -794,6 +796,7 @@ def func(field): right = getattr(other, field) return None if left == right else (left, right) + # Note that, we use "_members" not "_fields". values = [func(field) for field in self._members] # Perform lenient difference of the other parent members. result = super()._difference_lenient(other) diff --git a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py index 07f350bed7..0aefc066d6 100644 --- a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py @@ -12,8 +12,11 @@ # importing anything else. import iris.tests as tests +from copy import deepcopy import unittest.mock as mock +from unittest.mock import sentinel +from iris.common.lenient import LENIENT, qualname from iris.common.metadata import BaseMetadata, CoordMetadata @@ -69,5 +72,622 @@ def test_bases(self): self.assertTrue(issubclass(CoordMetadata, BaseMetadata)) +class Test___eq__(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + coord_system=sentinel.coord_system, + climatological=sentinel.climatological, + ) + self.dummy = sentinel.dummy + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.__eq__.__doc__, CoordMetadata.__eq__.__doc__, + ) + + def test_lenient_service(self): + qualname___eq__ = qualname(CoordMetadata.__eq__) + self.assertIn(qualname___eq__, LENIENT) + self.assertTrue(LENIENT[qualname___eq__]) + self.assertTrue(LENIENT[CoordMetadata.__eq__]) + + def test_call(self): + other = sentinel.other + return_value = sentinel.return_value + metadata = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = metadata.__eq__(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(), kwargs) + + def test_op_lenient_same(self): + lmetadata = CoordMetadata(**self.values) + rmetadata = CoordMetadata(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_same_none(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = CoordMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_same_members_none(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = None + rmetadata = CoordMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_different(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = CoordMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_different_members(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = self.dummy + rmetadata = CoordMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_same(self): + lmetadata = CoordMetadata(**self.values) + rmetadata = CoordMetadata(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = CoordMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_members(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = self.dummy + rmetadata = CoordMetadata(**right) + + with mock.patch( + "iris.common.metadata.LENIENT", return_value=False + ): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_none(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = CoordMetadata(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_members_none(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = None + rmetadata = CoordMetadata(**right) + + with mock.patch( + "iris.common.metadata.LENIENT", return_value=False + ): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + +class Test_combine(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + coord_system=sentinel.coord_system, + climatological=sentinel.climatological, + ) + self.dummy = sentinel.dummy + self.none = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.combine.__doc__, CoordMetadata.combine.__doc__, + ) + + def test_lenient_service(self): + qualname_combine = qualname(CoordMetadata.combine) + self.assertIn(qualname_combine, LENIENT) + self.assertTrue(LENIENT[qualname_combine]) + self.assertTrue(LENIENT[CoordMetadata.combine]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = self.none.combine(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = self.none.combine(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + def test_op_lenient_same(self): + lmetadata = CoordMetadata(**self.values) + rmetadata = CoordMetadata(**self.values) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_same_none(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = CoordMetadata(**right) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_same_members_none(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = None + rmetadata = CoordMetadata(**right) + expected = right.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue( + expected, lmetadata.combine(rmetadata)._asdict() + ) + self.assertTrue( + expected, rmetadata.combine(lmetadata)._asdict() + ) + + def test_op_lenient_different(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = CoordMetadata(**right) + expected = self.values.copy() + expected["units"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_different_members(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = self.dummy + rmetadata = CoordMetadata(**right) + expected = self.values.copy() + expected[member] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.combine(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.combine(lmetadata)._asdict() + ) + + def test_op_strict_same(self): + lmetadata = CoordMetadata(**self.values) + rmetadata = CoordMetadata(**self.values) + expected = self.values.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = CoordMetadata(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_members(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = self.dummy + rmetadata = CoordMetadata(**right) + expected = self.values.copy() + expected[member] = None + + with mock.patch( + "iris.common.metadata.LENIENT", return_value=False + ): + self.assertEqual( + expected, lmetadata.combine(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.combine(lmetadata)._asdict() + ) + + def test_op_strict_different_none(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = CoordMetadata(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_members_none(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right[member] = None + rmetadata = CoordMetadata(**right) + expected = self.values.copy() + expected[member] = None + + with mock.patch( + "iris.common.metadata.LENIENT", return_value=False + ): + self.assertEqual( + expected, lmetadata.combine(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.combine(lmetadata)._asdict() + ) + + +class Test_difference(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + coord_system=sentinel.coord_system, + climatological=sentinel.climatological, + ) + self.dummy = sentinel.dummy + self.none = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.difference.__doc__, CoordMetadata.difference.__doc__, + ) + + def test_lenient_service(self): + qualname_difference = qualname(CoordMetadata.difference) + self.assertIn(qualname_difference, LENIENT) + self.assertTrue(LENIENT[qualname_difference]) + self.assertTrue(LENIENT[CoordMetadata.difference]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = self.none.difference(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = self.none.difference(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + def test_op_lenient_same(self): + lmetadata = CoordMetadata(**self.values) + rmetadata = CoordMetadata(**self.values) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_same_none(self): + lmetadata = CoordMetadata(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = CoordMetadata(**right) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_same_members_none(self): + for member in CoordMetadata._members: + lmetadata = CoordMetadata(**self.values) + member_value = getattr(lmetadata, member) + right = self.values.copy() + right[member] = None + rmetadata = CoordMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected[member] = (member_value, None) + rexpected = deepcopy(self.none)._asdict() + rexpected[member] = (None, member_value) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_different(self): + left = self.values.copy() + lmetadata = CoordMetadata(**left) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = CoordMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["units"] = (left["units"], right["units"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["units"] = lexpected["units"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_different_members(self): + for member in CoordMetadata._members: + left = self.values.copy() + lmetadata = CoordMetadata(**left) + right = self.values.copy() + right[member] = self.dummy + rmetadata = CoordMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected[member] = (left[member], right[member]) + rexpected = deepcopy(self.none)._asdict() + rexpected[member] = lexpected[member][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_same(self): + lmetadata = CoordMetadata(**self.values) + rmetadata = CoordMetadata(**self.values) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different(self): + left = self.values.copy() + lmetadata = CoordMetadata(**left) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = CoordMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_members(self): + for member in CoordMetadata._members: + left = self.values.copy() + lmetadata = CoordMetadata(**left) + right = self.values.copy() + right[member] = self.dummy + rmetadata = CoordMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected[member] = (left[member], right[member]) + rexpected = deepcopy(self.none)._asdict() + rexpected[member] = lexpected[member][::-1] + + with mock.patch( + "iris.common.metadata.LENIENT", return_value=False + ): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_none(self): + left = self.values.copy() + lmetadata = CoordMetadata(**left) + right = self.values.copy() + right["long_name"] = None + rmetadata = CoordMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_members_none(self): + for member in CoordMetadata._members: + left = self.values.copy() + lmetadata = CoordMetadata(**left) + right = self.values.copy() + right[member] = None + rmetadata = CoordMetadata(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected[member] = (left[member], right[member]) + rexpected = deepcopy(self.none)._asdict() + rexpected[member] = lexpected[member][::-1] + + with mock.patch( + "iris.common.metadata.LENIENT", return_value=False + ): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + +class Test_equal(tests.IrisTest): + def setUp(self): + self.none = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.equal.__doc__, CoordMetadata.equal.__doc__ + ) + + def test_lenient_service(self): + qualname_equal = qualname(CoordMetadata.equal) + self.assertIn(qualname_equal, LENIENT) + self.assertTrue(LENIENT[qualname_equal]) + self.assertTrue(LENIENT[CoordMetadata.equal]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = self.none.equal(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = self.none.equal(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + if __name__ == "__main__": tests.main() From ded88ef73d08ec729c67b474d78a63060a35fba8 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 22 Jun 2020 15:48:58 +0100 Subject: [PATCH 27/45] add CubeMetadata test coverage --- .../unit/common/metadata/test_CubeMetadata.py | 588 ++++++++++++++++++ 1 file changed, 588 insertions(+) diff --git a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py index 5d3f39f570..c22b019f66 100644 --- a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py @@ -12,8 +12,11 @@ # importing anything else. import iris.tests as tests +from copy import deepcopy import unittest.mock as mock +from unittest.mock import sentinel +from iris.common.lenient import LENIENT, qualname from iris.common.metadata import BaseMetadata, CubeMetadata @@ -87,6 +90,591 @@ def test_bases(self): self.assertTrue(issubclass(CubeMetadata, BaseMetadata)) +class Test___eq__(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + # Must be a mapping. + attributes=dict(), + cell_methods=sentinel.cell_methods, + ) + self.dummy = sentinel.dummy + self.cls = CubeMetadata + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__, + ) + + def test_lenient_service(self): + qualname___eq__ = qualname(self.cls.__eq__) + self.assertIn(qualname___eq__, LENIENT) + self.assertTrue(LENIENT[qualname___eq__]) + self.assertTrue(LENIENT[self.cls.__eq__]) + + def test_call(self): + other = sentinel.other + return_value = sentinel.return_value + metadata = self.cls(*(None,) * len(self.cls._fields)) + with mock.patch.object( + BaseMetadata, "__eq__", return_value=return_value + ) as mocker: + result = metadata.__eq__(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(), kwargs) + + def test_op_lenient_same(self): + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_same_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_same_cell_methods_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = None + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_different(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_lenient_different_cell_methods(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = self.dummy + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_same(self): + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertTrue(lmetadata.__eq__(rmetadata)) + self.assertTrue(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_cell_methods(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = self.dummy + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + def test_op_strict_different_measure_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = None + rmetadata = self.cls(**right) + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertFalse(lmetadata.__eq__(rmetadata)) + self.assertFalse(rmetadata.__eq__(lmetadata)) + + +class Test_combine(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + cell_methods=sentinel.cell_methods, + ) + self.dummy = sentinel.dummy + self.cls = CubeMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.combine.__doc__, self.cls.combine.__doc__, + ) + + def test_lenient_service(self): + qualname_combine = qualname(self.cls.combine) + self.assertIn(qualname_combine, LENIENT) + self.assertTrue(LENIENT[qualname_combine]) + self.assertTrue(LENIENT[self.cls.combine]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = self.none.combine(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "combine", return_value=return_value + ) as mocker: + result = self.none.combine(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + def test_op_lenient_same(self): + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_same_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = self.cls(**right) + expected = self.values + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_same_cell_methods_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = None + rmetadata = self.cls(**right) + expected = right.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertTrue(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertTrue(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_different(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = self.cls(**right) + expected = self.values.copy() + expected["units"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_lenient_different_cell_methods(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = self.dummy + rmetadata = self.cls(**right) + expected = self.values.copy() + expected["cell_methods"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_same(self): + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) + expected = self.values.copy() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = self.cls(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_cell_methods(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = self.dummy + rmetadata = self.cls(**right) + expected = self.values.copy() + expected["cell_methods"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["long_name"] = None + rmetadata = self.cls(**right) + expected = self.values.copy() + expected["long_name"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + def test_op_strict_different_cell_methods_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = None + rmetadata = self.cls(**right) + expected = self.values.copy() + expected["cell_methods"] = None + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) + self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) + + +class Test_difference(tests.IrisTest): + def setUp(self): + self.values = dict( + standard_name=sentinel.standard_name, + long_name=sentinel.long_name, + var_name=sentinel.var_name, + units=sentinel.units, + attributes=sentinel.attributes, + cell_methods=sentinel.cell_methods, + ) + self.dummy = sentinel.dummy + self.cls = CubeMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) + + def test_wraps_docstring(self): + self.assertEqual( + BaseMetadata.difference.__doc__, self.cls.difference.__doc__, + ) + + def test_lenient_service(self): + qualname_difference = qualname(self.cls.difference) + self.assertIn(qualname_difference, LENIENT) + self.assertTrue(LENIENT[qualname_difference]) + self.assertTrue(LENIENT[self.cls.difference]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = self.none.difference(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "difference", return_value=return_value + ) as mocker: + result = self.none.difference(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + def test_op_lenient_same(self): + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_same_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["var_name"] = None + rmetadata = self.cls(**right) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_same_cell_methods_none(self): + lmetadata = self.cls(**self.values) + right = self.values.copy() + right["cell_methods"] = None + rmetadata = self.cls(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["cell_methods"] = (sentinel.cell_methods, None) + rexpected = deepcopy(self.none)._asdict() + rexpected["cell_methods"] = (None, sentinel.cell_methods) + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_different(self): + left = self.values.copy() + lmetadata = self.cls(**left) + right = self.values.copy() + right["units"] = self.dummy + rmetadata = self.cls(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["units"] = (left["units"], right["units"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["units"] = lexpected["units"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_lenient_different_cell_methods(self): + left = self.values.copy() + lmetadata = self.cls(**left) + right = self.values.copy() + right["cell_methods"] = self.dummy + rmetadata = self.cls(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["cell_methods"] = ( + left["cell_methods"], + right["cell_methods"], + ) + rexpected = deepcopy(self.none)._asdict() + rexpected["cell_methods"] = lexpected["cell_methods"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=True): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_same(self): + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) + expected = deepcopy(self.none)._asdict() + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + expected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + expected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different(self): + left = self.values.copy() + lmetadata = self.cls(**left) + right = self.values.copy() + right["long_name"] = self.dummy + rmetadata = self.cls(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_cell_methods(self): + left = self.values.copy() + lmetadata = self.cls(**left) + right = self.values.copy() + right["cell_methods"] = self.dummy + rmetadata = self.cls(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["cell_methods"] = ( + left["cell_methods"], + right["cell_methods"], + ) + rexpected = deepcopy(self.none)._asdict() + rexpected["cell_methods"] = lexpected["cell_methods"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_none(self): + left = self.values.copy() + lmetadata = self.cls(**left) + right = self.values.copy() + right["long_name"] = None + rmetadata = self.cls(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["long_name"] = (left["long_name"], right["long_name"]) + rexpected = deepcopy(self.none)._asdict() + rexpected["long_name"] = lexpected["long_name"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + def test_op_strict_different_measure_none(self): + left = self.values.copy() + lmetadata = self.cls(**left) + right = self.values.copy() + right["cell_methods"] = None + rmetadata = self.cls(**right) + lexpected = deepcopy(self.none)._asdict() + lexpected["cell_methods"] = ( + left["cell_methods"], + right["cell_methods"], + ) + rexpected = deepcopy(self.none)._asdict() + rexpected["cell_methods"] = lexpected["cell_methods"][::-1] + + with mock.patch("iris.common.metadata.LENIENT", return_value=False): + self.assertEqual( + lexpected, lmetadata.difference(rmetadata)._asdict() + ) + self.assertEqual( + rexpected, rmetadata.difference(lmetadata)._asdict() + ) + + +class Test_equal(tests.IrisTest): + def setUp(self): + self.cls = CubeMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) + + def test_wraps_docstring(self): + self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) + + def test_lenient_service(self): + qualname_equal = qualname(self.cls.equal) + self.assertIn(qualname_equal, LENIENT) + self.assertTrue(LENIENT[qualname_equal]) + self.assertTrue(LENIENT[self.cls.equal]) + + def test_lenient_default(self): + other = sentinel.other + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = self.none.equal(other) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=None), kwargs) + + def test_lenient(self): + other = sentinel.other + lenient = sentinel.lenient + return_value = sentinel.return_value + with mock.patch.object( + BaseMetadata, "equal", return_value=return_value + ) as mocker: + result = self.none.equal(other, lenient=lenient) + + self.assertEqual(return_value, result) + self.assertEqual(1, mocker.call_count) + (arg,), kwargs = mocker.call_args + self.assertEqual(other, arg) + self.assertEqual(dict(lenient=lenient), kwargs) + + class Test_name(tests.IrisTest): def setUp(self): self.default = CubeMetadata.DEFAULT_NAME From f1c338f9516647023041a81dfdfe24a6e0c93e68 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 22 Jun 2020 21:17:38 +0100 Subject: [PATCH 28/45] metadata tests use self.cls --- .../test_AncillaryVariableMetadata.py | 128 ++++---- .../unit/common/metadata/test_BaseMetadata.py | 299 +++++++++--------- .../metadata/test_CellMeasureMetadata.py | 174 +++++----- .../common/metadata/test_CoordMetadata.py | 189 +++++------ .../unit/common/metadata/test_CubeMetadata.py | 7 +- 5 files changed, 400 insertions(+), 397 deletions(-) diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index cb91e83428..ce9262e44e 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -27,9 +27,10 @@ def setUp(self): self.var_name = mock.sentinel.var_name self.units = mock.sentinel.units self.attributes = mock.sentinel.attributes + self.cls = AncillaryVariableMetadata def test_repr(self): - metadata = AncillaryVariableMetadata( + metadata = self.cls( standard_name=self.standard_name, long_name=self.long_name, var_name=self.var_name, @@ -57,10 +58,10 @@ def test__fields(self): "units", "attributes", ) - self.assertEqual(AncillaryVariableMetadata._fields, expected) + self.assertEqual(self.cls._fields, expected) def test_bases(self): - self.assertTrue(issubclass(AncillaryVariableMetadata, BaseMetadata)) + self.assertTrue(issubclass(self.cls, BaseMetadata)) class Test___eq__(tests.IrisTest): @@ -73,25 +74,23 @@ def setUp(self): attributes=sentinel.attributes, ) self.dummy = sentinel.dummy + self.cls = AncillaryVariableMetadata def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.__eq__.__doc__, - AncillaryVariableMetadata.__eq__.__doc__, + BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__, ) def test_lenient_service(self): - qualname___eq__ = qualname(AncillaryVariableMetadata.__eq__) + qualname___eq__ = qualname(self.cls.__eq__) self.assertIn(qualname___eq__, LENIENT) self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[AncillaryVariableMetadata.__eq__]) + self.assertTrue(LENIENT[self.cls.__eq__]) def test_call(self): other = sentinel.other return_value = sentinel.return_value - metadata = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) + metadata = self.cls(*(None,) * len(self.cls._fields)) with mock.patch.object( BaseMetadata, "__eq__", return_value=return_value ) as mocker: @@ -104,56 +103,56 @@ def test_call(self): self.assertEqual(dict(), kwargs) def test_op_lenient_same(self): - lmetadata = AncillaryVariableMetadata(**self.values) - rmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_lenient_same_none(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_lenient_different(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_same(self): - lmetadata = AncillaryVariableMetadata(**self.values) - rmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_strict_different(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_different_none(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) @@ -170,21 +169,19 @@ def setUp(self): attributes=sentinel.attributes, ) self.dummy = sentinel.dummy - self.none = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) + self.cls = AncillaryVariableMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.combine.__doc__, - AncillaryVariableMetadata.combine.__doc__, + BaseMetadata.combine.__doc__, self.cls.combine.__doc__, ) def test_lenient_service(self): - qualname_combine = qualname(AncillaryVariableMetadata.combine) + qualname_combine = qualname(self.cls.combine) self.assertIn(qualname_combine, LENIENT) self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[AncillaryVariableMetadata.combine]) + self.assertTrue(LENIENT[self.cls.combine]) def test_lenient_default(self): other = sentinel.other @@ -216,8 +213,8 @@ def test_lenient(self): self.assertEqual(dict(lenient=lenient), kwargs) def test_op_lenient_same(self): - lmetadata = AncillaryVariableMetadata(**self.values) - rmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = self.values with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -225,10 +222,10 @@ def test_op_lenient_same(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_same_none(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) expected = self.values with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -236,10 +233,10 @@ def test_op_lenient_same_none(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_different(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["units"] = None @@ -248,8 +245,8 @@ def test_op_lenient_different(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_same(self): - lmetadata = AncillaryVariableMetadata(**self.values) - rmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = self.values.copy() with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -257,10 +254,10 @@ def test_op_strict_same(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["long_name"] = None @@ -269,10 +266,10 @@ def test_op_strict_different(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different_none(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["long_name"] = None @@ -291,21 +288,19 @@ def setUp(self): attributes=sentinel.attributes, ) self.dummy = sentinel.dummy - self.none = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) + self.cls = AncillaryVariableMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.difference.__doc__, - AncillaryVariableMetadata.difference.__doc__, + BaseMetadata.difference.__doc__, self.cls.difference.__doc__, ) def test_lenient_service(self): - qualname_difference = qualname(AncillaryVariableMetadata.difference) + qualname_difference = qualname(self.cls.difference) self.assertIn(qualname_difference, LENIENT) self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[AncillaryVariableMetadata.difference]) + self.assertTrue(LENIENT[self.cls.difference]) def test_lenient_default(self): other = sentinel.other @@ -337,8 +332,8 @@ def test_lenient(self): self.assertEqual(dict(lenient=lenient), kwargs) def test_op_lenient_same(self): - lmetadata = AncillaryVariableMetadata(**self.values) - rmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -350,10 +345,10 @@ def test_op_lenient_same(self): ) def test_op_lenient_same_none(self): - lmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -366,10 +361,10 @@ def test_op_lenient_same_none(self): def test_op_lenient_different(self): left = self.values.copy() - lmetadata = AncillaryVariableMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["units"] = self.dummy - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["units"] = (left["units"], right["units"]) rexpected = deepcopy(self.none)._asdict() @@ -384,8 +379,8 @@ def test_op_lenient_different(self): ) def test_op_strict_same(self): - lmetadata = AncillaryVariableMetadata(**self.values) - rmetadata = AncillaryVariableMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -398,10 +393,10 @@ def test_op_strict_same(self): def test_op_strict_different(self): left = self.values.copy() - lmetadata = AncillaryVariableMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) rexpected = deepcopy(self.none)._asdict() @@ -417,10 +412,10 @@ def test_op_strict_different(self): def test_op_strict_different_none(self): left = self.values.copy() - lmetadata = AncillaryVariableMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["long_name"] = None - rmetadata = AncillaryVariableMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) rexpected = deepcopy(self.none)._asdict() @@ -437,20 +432,17 @@ def test_op_strict_different_none(self): class Test_equal(tests.IrisTest): def setUp(self): - self.none = AncillaryVariableMetadata( - *(None,) * len(AncillaryVariableMetadata._fields) - ) + self.cls = AncillaryVariableMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.equal.__doc__, AncillaryVariableMetadata.equal.__doc__ - ) + self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) def test_lenient_service(self): - qualname_equal = qualname(AncillaryVariableMetadata.equal) + qualname_equal = qualname(self.cls.equal) self.assertIn(qualname_equal, LENIENT) self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue(LENIENT[AncillaryVariableMetadata.equal]) + self.assertTrue(LENIENT[self.cls.equal]) def test_lenient_default(self): other = sentinel.other diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 074f96ce13..c534c3acd4 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -28,9 +28,10 @@ def setUp(self): self.var_name = mock.sentinel.var_name self.units = mock.sentinel.units self.attributes = mock.sentinel.attributes + self.cls = BaseMetadata def test_repr(self): - metadata = BaseMetadata( + metadata = self.cls( standard_name=self.standard_name, long_name=self.long_name, var_name=self.var_name, @@ -58,7 +59,7 @@ def test__fields(self): "units", "attributes", ) - self.assertEqual(expected, BaseMetadata._fields) + self.assertEqual(expected, self.cls._fields) class Test___eq__(tests.IrisTest): @@ -70,13 +71,14 @@ def setUp(self): units=sentinel.units, attributes=sentinel.attributes, ) - self.metadata = BaseMetadata(**self.kwargs) + self.cls = BaseMetadata + self.metadata = self.cls(**self.kwargs) def test_lenient_service(self): - qualname___eq__ = qualname(BaseMetadata.__eq__) + qualname___eq__ = qualname(self.cls.__eq__) self.assertIn(qualname___eq__, LENIENT) self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[BaseMetadata.__eq__]) + self.assertTrue(LENIENT[self.cls.__eq__]) def test_cannot_compare_non_class(self): result = self.metadata.__eq__(None) @@ -93,7 +95,7 @@ def test_lenient(self): "iris.common.metadata.LENIENT", return_value=True ) as mlenient: with mock.patch.object( - BaseMetadata, "_compare_lenient", return_value=return_value + self.cls, "_compare_lenient", return_value=return_value ) as mcompare: result = self.metadata.__eq__(self.metadata) @@ -105,7 +107,7 @@ def test_lenient(self): self.assertEqual(1, mlenient.call_count) (arg,), kwargs = mlenient.call_args - self.assertEqual(qualname(BaseMetadata.__eq__), qualname(arg)) + self.assertEqual(qualname(self.cls.__eq__), qualname(arg)) self.assertEqual(dict(), kwargs) def test_strict_same(self): @@ -116,16 +118,17 @@ def test_strict_same(self): def test_strict_different(self): self.kwargs["var_name"] = None - other = BaseMetadata(**self.kwargs) + other = self.cls(**self.kwargs) self.assertFalse(self.metadata.__eq__(other)) self.assertFalse(other.__eq__(self.metadata)) class Test___lt__(tests.IrisTest): def setUp(self): - self.one = BaseMetadata(1, 1, 1, 1, 1) - self.two = BaseMetadata(1, 1, 1, 1, 2) - self.none = BaseMetadata(1, 1, 1, 1, None) + self.cls = BaseMetadata + self.one = self.cls(1, 1, 1, 1, 1) + self.two = self.cls(1, 1, 1, 1, 2) + self.none = self.cls(1, 1, 1, 1, None) def test__ascending_lt(self): result = self.one < self.two @@ -146,13 +149,14 @@ def test__none_lhs_operand(self): class Test___ne__(tests.IrisTest): def setUp(self): - self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.cls = BaseMetadata + self.metadata = self.cls(*(None,) * len(self.cls._fields)) self.other = sentinel.other def test_notimplemented(self): return_value = NotImplemented with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value + self.cls, "__eq__", return_value=return_value ) as mocker: result = self.metadata.__ne__(self.other) @@ -165,7 +169,7 @@ def test_notimplemented(self): def test_negate_true(self): return_value = True with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value + self.cls, "__eq__", return_value=return_value ) as mocker: result = self.metadata.__ne__(self.other) @@ -177,7 +181,7 @@ def test_negate_true(self): def test_negate_false(self): return_value = False with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value + self.cls, "__eq__", return_value=return_value ) as mocker: result = self.metadata.__ne__(self.other) @@ -196,7 +200,8 @@ def setUp(self): units="units", attributes=dict(one=sentinel.one, two=sentinel.two), ) - self.metadata = BaseMetadata(**self.kwargs) + self.cls = BaseMetadata + self.metadata = self.cls(**self.kwargs) def test_lenient(self): return_value = sentinel._combine_lenient @@ -205,7 +210,7 @@ def test_lenient(self): "iris.common.metadata.LENIENT", return_value=True ) as mlenient: with mock.patch.object( - BaseMetadata, "_combine_lenient", return_value=return_value + self.cls, "_combine_lenient", return_value=return_value ) as mcombine: result = self.metadata._combine(other) @@ -226,22 +231,21 @@ def test_strict(self): values["standard_name"] = dummy values["var_name"] = dummy values["attributes"] = dummy - other = BaseMetadata(**values) + other = self.cls(**values) with mock.patch("iris.common.metadata.LENIENT", return_value=False): result = self.metadata._combine(other) expected = [ None if values[field] == dummy else values[field] - for field in BaseMetadata._fields + for field in self.cls._fields ] self.assertEqual(expected, result) class Test__combine_lenient(tests.IrisTest): def setUp(self): - self.none = BaseMetadata( - *(None,) * len(BaseMetadata._fields) - )._asdict() + self.cls = BaseMetadata + self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() self.names = dict( standard_name=sentinel.standard_name, long_name=sentinel.long_name, @@ -252,8 +256,8 @@ def test_strict_units(self): left = self.none.copy() left["units"] = "K" right = left.copy() - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) @@ -264,8 +268,8 @@ def test_strict_units_different(self): right = self.none.copy() left["units"] = "K" right["units"] = "km" - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.values()) @@ -277,8 +281,8 @@ def test_strict_units_different_none(self): left = self.none.copy() right = self.none.copy() left["units"] = "K" - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.values()) @@ -294,14 +298,12 @@ def test_attributes(self): rdict = dict(item=sentinel.right) left["attributes"] = ldict right["attributes"] = rdict - rmetadata = BaseMetadata(**right) + rmetadata = self.cls(**right) return_value = sentinel.return_value with mock.patch.object( - BaseMetadata, - "_combine_lenient_attributes", - return_value=return_value, + self.cls, "_combine_lenient_attributes", return_value=return_value, ) as mocker: - lmetadata = BaseMetadata(**left) + lmetadata = self.cls(**left) result = lmetadata._combine_lenient(rmetadata) expected = self.none.copy() @@ -322,8 +324,8 @@ def test_attributes_non_mapping_different(self): rdict = sentinel.right left["attributes"] = ldict right["attributes"] = rdict - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.copy().values()) @@ -334,8 +336,8 @@ def test_attributes_non_mapping_different_none(self): right = self.none.copy() ldict = dict(item=sentinel.left) left["attributes"] = ldict - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = self.none.copy() @@ -350,8 +352,8 @@ def test_names(self): left = self.none.copy() left.update(self.names) right = left.copy() - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) @@ -365,8 +367,8 @@ def test_names_different(self): right["standard_name"] = dummy right["long_name"] = dummy right["var_name"] = dummy - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.copy().values()) @@ -376,8 +378,8 @@ def test_names_different_none(self): left = self.none.copy() right = self.none.copy() left.update(self.names) - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) @@ -396,7 +398,8 @@ def setUp(self): four=sentinel.four, five=sentinel.five, ) - self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.cls = BaseMetadata + self.metadata = self.cls(*(None,) * len(self.cls._fields)) self.dummy = sentinel.dummy def test_same(self): @@ -444,9 +447,8 @@ def test_extra(self): class Test__compare_lenient(tests.IrisTest): def setUp(self): - self.none = BaseMetadata( - *(None,) * len(BaseMetadata._fields) - )._asdict() + self.cls = BaseMetadata + self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() self.names = dict( standard_name=sentinel.standard_name, long_name=sentinel.long_name, @@ -457,16 +459,16 @@ def test_name_same(self): left = self.none.copy() left.update(self.names) right = left.copy() - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) with mock.patch.object( - BaseMetadata, "_is_attributes", return_value=False + self.cls, "_is_attributes", return_value=False ) as mocker: self.assertTrue(lmetadata._compare_lenient(rmetadata)) self.assertTrue(rmetadata._compare_lenient(lmetadata)) - expected = (len(BaseMetadata._fields) - 1) * 2 + expected = (len(self.cls._fields) - 1) * 2 self.assertEqual(expected, mocker.call_count) def test_name_same_lenient_false(self): @@ -474,16 +476,16 @@ def test_name_same_lenient_false(self): left.update(self.names) right = self.none.copy() right["long_name"] = sentinel.standard_name - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) with mock.patch.object( - BaseMetadata, "_is_attributes", return_value=False + self.cls, "_is_attributes", return_value=False ) as mocker: self.assertFalse(lmetadata._compare_lenient(rmetadata)) self.assertFalse(rmetadata._compare_lenient(lmetadata)) - expected = (len(BaseMetadata._fields) - 1) * 2 + expected = (len(self.cls._fields) - 1) * 2 self.assertEqual(expected, mocker.call_count) def test_name_different(self): @@ -491,10 +493,10 @@ def test_name_different(self): left.update(self.names) right = left.copy() right["standard_name"] = None - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) - with mock.patch.object(BaseMetadata, "_is_attributes") as mocker: + with mock.patch.object(self.cls, "_is_attributes") as mocker: self.assertFalse(lmetadata._compare_lenient(rmetadata)) self.assertFalse(rmetadata._compare_lenient(lmetadata)) @@ -505,16 +507,16 @@ def test_strict_units(self): left.update(self.names) left["units"] = "K" right = left.copy() - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) with mock.patch.object( - BaseMetadata, "_is_attributes", return_value=False + self.cls, "_is_attributes", return_value=False ) as mocker: self.assertTrue(lmetadata._compare_lenient(rmetadata)) self.assertTrue(rmetadata._compare_lenient(lmetadata)) - expected = (len(BaseMetadata._fields) - 1) * 2 + expected = (len(self.cls._fields) - 1) * 2 self.assertEqual(expected, mocker.call_count) def test_strict_units_different(self): @@ -523,16 +525,16 @@ def test_strict_units_different(self): left["units"] = "K" right = left.copy() right["units"] = "m" - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) with mock.patch.object( - BaseMetadata, "_is_attributes", return_value=False + self.cls, "_is_attributes", return_value=False ) as mocker: self.assertFalse(lmetadata._compare_lenient(rmetadata)) self.assertFalse(rmetadata._compare_lenient(lmetadata)) - expected = (len(BaseMetadata._fields) - 1) * 2 + expected = (len(self.cls._fields) - 1) * 2 self.assertEqual(expected, mocker.call_count) def test_attributes(self): @@ -543,11 +545,11 @@ def test_attributes(self): rdict = dict(item=sentinel.right) left["attributes"] = ldict right["attributes"] = rdict - rmetadata = BaseMetadata(**right) + rmetadata = self.cls(**right) with mock.patch.object( - BaseMetadata, "_compare_lenient_attributes", return_value=True, + self.cls, "_compare_lenient_attributes", return_value=True, ) as mocker: - lmetadata = BaseMetadata(**left) + lmetadata = self.cls(**left) self.assertTrue(lmetadata._compare_lenient(rmetadata)) self.assertTrue(rmetadata._compare_lenient(lmetadata)) @@ -563,8 +565,8 @@ def test_attributes_non_mapping_different(self): rdict = sentinel.right left["attributes"] = ldict right["attributes"] = rdict - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) self.assertFalse(lmetadata._compare_lenient(rmetadata)) self.assertFalse(rmetadata._compare_lenient(lmetadata)) @@ -575,8 +577,8 @@ def test_attributes_non_mapping_different_none(self): right = left.copy() ldict = dict(item=sentinel.left) left["attributes"] = ldict - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) self.assertTrue(lmetadata._compare_lenient(rmetadata)) self.assertTrue(rmetadata._combine_lenient(lmetadata)) @@ -587,8 +589,8 @@ def test_names(self): left["long_name"] = None right = self.none.copy() right["long_name"] = left["standard_name"] - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) self.assertTrue(lmetadata._compare_lenient(rmetadata)) self.assertTrue(rmetadata._combine_lenient(lmetadata)) @@ -603,7 +605,8 @@ def setUp(self): four=sentinel.four, five=sentinel.five, ) - self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.cls = BaseMetadata + self.metadata = self.cls(*(None,) * len(self.cls._fields)) self.dummy = sentinel.dummy def test_same(self): @@ -656,7 +659,8 @@ def setUp(self): units="units", attributes=dict(one=sentinel.one, two=sentinel.two), ) - self.metadata = BaseMetadata(**self.kwargs) + self.cls = BaseMetadata + self.metadata = self.cls(**self.kwargs) def test_lenient(self): return_value = sentinel._difference_lenient @@ -665,7 +669,7 @@ def test_lenient(self): "iris.common.metadata.LENIENT", return_value=True ) as mlenient: with mock.patch.object( - BaseMetadata, "_difference_lenient", return_value=return_value + self.cls, "_difference_lenient", return_value=return_value ) as mdifference: result = self.metadata._difference(other) @@ -685,17 +689,17 @@ def test_strict(self): values = self.kwargs.copy() values["long_name"] = dummy values["units"] = dummy - other = BaseMetadata(**values) + other = self.cls(**values) method = "_difference_strict_attributes" with mock.patch("iris.common.metadata.LENIENT", return_value=False): with mock.patch.object( - BaseMetadata, method, return_value=None + self.cls, method, return_value=None ) as mdifference: result = self.metadata._difference(other) expected = [ (self.kwargs[field], dummy) if values[field] == dummy else None - for field in BaseMetadata._fields + for field in self.cls._fields ] self.assertEqual(expected, result) self.assertEqual(1, mdifference.call_count) @@ -705,13 +709,13 @@ def test_strict(self): self.assertEqual(dict(), kwargs) with mock.patch.object( - BaseMetadata, method, return_value=None + self.cls, method, return_value=None ) as mdifference: result = other._difference(self.metadata) expected = [ (dummy, self.kwargs[field]) if values[field] == dummy else None - for field in BaseMetadata._fields + for field in self.cls._fields ] self.assertEqual(expected, result) self.assertEqual(1, mdifference.call_count) @@ -723,9 +727,8 @@ def test_strict(self): class Test__difference_lenient(tests.IrisTest): def setUp(self): - self.none = BaseMetadata( - *(None,) * len(BaseMetadata._fields) - )._asdict() + self.cls = BaseMetadata + self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() self.names = dict( standard_name=sentinel.standard_name, long_name=sentinel.long_name, @@ -736,8 +739,8 @@ def test_strict_units(self): left = self.none.copy() left["units"] = "km" right = left.copy() - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.values()) self.assertEqual(expected, result) @@ -748,8 +751,8 @@ def test_strict_units_different(self): lunits, runits = "m", "km" left["units"] = lunits right["units"] = runits - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = self.none.copy() @@ -768,8 +771,8 @@ def test_strict_units_different_none(self): right = self.none.copy() lunits, runits = "m", None left["units"] = lunits - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = self.none.copy() @@ -790,14 +793,14 @@ def test_attributes(self): rdict = dict(item=sentinel.right) left["attributes"] = ldict right["attributes"] = rdict - rmetadata = BaseMetadata(**right) + rmetadata = self.cls(**right) return_value = sentinel.return_value with mock.patch.object( - BaseMetadata, + self.cls, "_difference_lenient_attributes", return_value=return_value, ) as mocker: - lmetadata = BaseMetadata(**left) + lmetadata = self.cls(**left) result = lmetadata._difference_lenient(rmetadata) expected = self.none.copy() @@ -818,8 +821,8 @@ def test_attributes_non_mapping_different(self): rdict = sentinel.right left["attributes"] = ldict right["attributes"] = rdict - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = self.none.copy() @@ -838,8 +841,8 @@ def test_attributes_non_mapping_different_none(self): right = self.none.copy() ldict = dict(item=sentinel.left) left["attributes"] = ldict - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.copy().values()) @@ -852,8 +855,8 @@ def test_names(self): left = self.none.copy() left.update(self.names) right = left.copy() - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.values()) @@ -867,8 +870,8 @@ def test_names_different(self): right["standard_name"] = dummy right["long_name"] = dummy right["var_name"] = dummy - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = self.none.copy() @@ -896,8 +899,8 @@ def test_names_different_none(self): left = self.none.copy() right = self.none.copy() left.update(self.names) - lmetadata = BaseMetadata(**left) - rmetadata = BaseMetadata(**right) + lmetadata = self.cls(**left) + rmetadata = self.cls(**right) result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.values()) @@ -916,7 +919,8 @@ def setUp(self): four=sentinel.four, five=sentinel.five, ) - self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.cls = BaseMetadata + self.metadata = self.cls(*(None,) * len(self.cls._fields)) self.dummy = sentinel.dummy def test_same(self): @@ -978,7 +982,8 @@ def setUp(self): four=sentinel.four, five=sentinel.five, ) - self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.cls = BaseMetadata + self.metadata = self.cls(*(None,) * len(self.cls._fields)) self.dummy = sentinel.dummy def test_same(self): @@ -1055,7 +1060,8 @@ def test_extra(self): class Test__is_attributes(tests.IrisTest): def setUp(self): - self.metadata = BaseMetadata(*(None,) * len(BaseMetadata._fields)) + self.cls = BaseMetadata + self.metadata = self.cls(*(None,) * len(self.cls._fields)) self.field = "attributes" def test_field(self): @@ -1080,7 +1086,8 @@ def setUp(self): units="units", attributes="attributes", ) - self.metadata = BaseMetadata(**kwargs) + self.cls = BaseMetadata + self.metadata = self.cls(**kwargs) self.mock_kwargs = OrderedDict( standard_name=sentinel.standard_name, long_name=sentinel.long_name, @@ -1090,10 +1097,10 @@ def setUp(self): ) def test_lenient_service(self): - qualname_combine = qualname(BaseMetadata.combine) + qualname_combine = qualname(self.cls.combine) self.assertIn(qualname_combine, LENIENT) self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[BaseMetadata.combine]) + self.assertTrue(LENIENT[self.cls.combine]) def test_cannot_combine_non_class(self): emsg = "Cannot combine" @@ -1109,7 +1116,7 @@ def test_cannot_combine(self): def test_lenient_default(self): return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_combine", return_value=return_value + self.cls, "_combine", return_value=return_value ) as mocker: result = self.metadata.combine(self.metadata) @@ -1122,14 +1129,14 @@ def test_lenient_default(self): def test_lenient_true(self): return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_combine", return_value=return_value + self.cls, "_combine", return_value=return_value ) as mcombine: with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.combine(self.metadata, lenient=True) self.assertEqual(1, mcontext.call_count) (arg,), kwargs = mcontext.call_args - self.assertEqual(qualname(BaseMetadata.combine), arg) + self.assertEqual(qualname(self.cls.combine), arg) self.assertEqual(dict(), kwargs) self.assertEqual(result._asdict(), self.mock_kwargs) @@ -1141,7 +1148,7 @@ def test_lenient_true(self): def test_lenient_false(self): return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_combine", return_value=return_value + self.cls, "_combine", return_value=return_value ) as mcombine: with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.combine(self.metadata, lenient=False) @@ -1149,7 +1156,7 @@ def test_lenient_false(self): self.assertEqual(1, mcontext.call_count) args, kwargs = mcontext.call_args self.assertEqual((), args) - self.assertEqual({qualname(BaseMetadata.combine): False}, kwargs) + self.assertEqual({qualname(self.cls.combine): False}, kwargs) self.assertEqual(self.mock_kwargs, result._asdict()) self.assertEqual(1, mcombine.call_count) @@ -1167,7 +1174,8 @@ def setUp(self): units="units", attributes="attributes", ) - self.metadata = BaseMetadata(**kwargs) + self.cls = BaseMetadata + self.metadata = self.cls(**kwargs) self.mock_kwargs = OrderedDict( standard_name=sentinel.standard_name, long_name=sentinel.long_name, @@ -1177,10 +1185,10 @@ def setUp(self): ) def test_lenient_service(self): - qualname_difference = qualname(BaseMetadata.difference) + qualname_difference = qualname(self.cls.difference) self.assertIn(qualname_difference, LENIENT) self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[BaseMetadata.difference]) + self.assertTrue(LENIENT[self.cls.difference]) def test_cannot_differ_non_class(self): emsg = "Cannot differ" @@ -1196,7 +1204,7 @@ def test_cannot_differ(self): def test_lenient_default(self): return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_difference", return_value=return_value + self.cls, "_difference", return_value=return_value ) as mocker: result = self.metadata.difference(self.metadata) @@ -1209,14 +1217,14 @@ def test_lenient_default(self): def test_lenient_true(self): return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_difference", return_value=return_value + self.cls, "_difference", return_value=return_value ) as mdifference: with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.difference(self.metadata, lenient=True) self.assertEqual(1, mcontext.call_count) (arg,), kwargs = mcontext.call_args - self.assertEqual(qualname(BaseMetadata.difference), arg) + self.assertEqual(qualname(self.cls.difference), arg) self.assertEqual(dict(), kwargs) self.assertEqual(self.mock_kwargs, result._asdict()) @@ -1228,7 +1236,7 @@ def test_lenient_true(self): def test_lenient_false(self): return_value = self.mock_kwargs.values() with mock.patch.object( - BaseMetadata, "_difference", return_value=return_value + self.cls, "_difference", return_value=return_value ) as mdifference: with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.difference(self.metadata, lenient=False) @@ -1236,7 +1244,7 @@ def test_lenient_false(self): self.assertEqual(mcontext.call_count, 1) args, kwargs = mcontext.call_args self.assertEqual((), args) - self.assertEqual({qualname(BaseMetadata.difference): False}, kwargs) + self.assertEqual({qualname(self.cls.difference): False}, kwargs) self.assertEqual(self.mock_kwargs, result._asdict()) self.assertEqual(1, mdifference.call_count) @@ -1254,13 +1262,14 @@ def setUp(self): units=sentinel.units, attributes=sentinel.attributes, ) - self.metadata = BaseMetadata(**kwargs) + self.cls = BaseMetadata + self.metadata = self.cls(**kwargs) def test_lenient_service(self): - qualname_equal = qualname(BaseMetadata.equal) + qualname_equal = qualname(self.cls.equal) self.assertIn(qualname_equal, LENIENT) self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue((LENIENT[BaseMetadata.equal])) + self.assertTrue((LENIENT[self.cls.equal])) def test_cannot_compare_non_class(self): emsg = "Cannot compare" @@ -1276,7 +1285,7 @@ def test_cannot_compare(self): def test_lenient_default(self): return_value = sentinel.return_value with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value + self.cls, "__eq__", return_value=return_value ) as mocker: result = self.metadata.equal(self.metadata) @@ -1289,7 +1298,7 @@ def test_lenient_default(self): def test_lenient_true(self): return_value = sentinel.return_value with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value + self.cls, "__eq__", return_value=return_value ) as m__eq__: with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.equal(self.metadata, lenient=True) @@ -1297,7 +1306,7 @@ def test_lenient_true(self): self.assertEqual(return_value, result) self.assertEqual(1, mcontext.call_count) (arg,), kwargs = mcontext.call_args - self.assertEqual(qualname(BaseMetadata.equal), arg) + self.assertEqual(qualname(self.cls.equal), arg) self.assertEqual(dict(), kwargs) self.assertEqual(1, m__eq__.call_count) @@ -1308,7 +1317,7 @@ def test_lenient_true(self): def test_lenient_false(self): return_value = sentinel.return_value with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value + self.cls, "__eq__", return_value=return_value ) as m__eq__: with mock.patch.object(LENIENT, "context") as mcontext: result = self.metadata.equal(self.metadata, lenient=False) @@ -1316,7 +1325,7 @@ def test_lenient_false(self): self.assertEqual(1, mcontext.call_count) args, kwargs = mcontext.call_args self.assertEqual((), args) - self.assertEqual({qualname(BaseMetadata.equal): False}, kwargs) + self.assertEqual({qualname(self.cls.equal): False}, kwargs) self.assertEqual(return_value, result) self.assertEqual(1, m__eq__.call_count) @@ -1327,7 +1336,8 @@ def test_lenient_false(self): class Test_name(tests.IrisTest): def setUp(self): - self.default = BaseMetadata.DEFAULT_NAME + self.cls = BaseMetadata + self.default = self.cls.DEFAULT_NAME @staticmethod def _make(standard_name=None, long_name=None, var_name=None): @@ -1414,47 +1424,50 @@ def test_default__invalid_token(self): class Test_token(tests.IrisTest): + def setUp(self): + self.cls = BaseMetadata + def test_passthru_None(self): - result = BaseMetadata.token(None) + result = self.cls.token(None) self.assertIsNone(result) def test_fail_leading_underscore(self): - result = BaseMetadata.token("_nope") + result = self.cls.token("_nope") self.assertIsNone(result) def test_fail_leading_dot(self): - result = BaseMetadata.token(".nope") + result = self.cls.token(".nope") self.assertIsNone(result) def test_fail_leading_plus(self): - result = BaseMetadata.token("+nope") + result = self.cls.token("+nope") self.assertIsNone(result) def test_fail_leading_at(self): - result = BaseMetadata.token("@nope") + result = self.cls.token("@nope") self.assertIsNone(result) def test_fail_space(self): - result = BaseMetadata.token("nope nope") + result = self.cls.token("nope nope") self.assertIsNone(result) def test_fail_colon(self): - result = BaseMetadata.token("nope:") + result = self.cls.token("nope:") self.assertIsNone(result) def test_pass_simple(self): token = "simple" - result = BaseMetadata.token(token) + result = self.cls.token(token) self.assertEqual(token, result) def test_pass_leading_digit(self): token = "123simple" - result = BaseMetadata.token(token) + result = self.cls.token(token) self.assertEqual(token, result) def test_pass_mixture(self): token = "S.imple@one+two_3" - result = BaseMetadata.token(token) + result = self.cls.token(token) self.assertEqual(token, result) diff --git a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py index 63a5bb99d3..f6afecc559 100644 --- a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py @@ -28,9 +28,10 @@ def setUp(self): self.units = mock.sentinel.units self.attributes = mock.sentinel.attributes self.measure = mock.sentinel.measure + self.cls = CellMeasureMetadata def test_repr(self): - metadata = CellMeasureMetadata( + metadata = self.cls( standard_name=self.standard_name, long_name=self.long_name, var_name=self.var_name, @@ -61,10 +62,10 @@ def test__fields(self): "attributes", "measure", ) - self.assertEqual(CellMeasureMetadata._fields, expected) + self.assertEqual(self.cls._fields, expected) def test_bases(self): - self.assertTrue(issubclass(CellMeasureMetadata, BaseMetadata)) + self.assertTrue(issubclass(self.cls, BaseMetadata)) class Test___eq__(tests.IrisTest): @@ -78,24 +79,23 @@ def setUp(self): measure=sentinel.measure, ) self.dummy = sentinel.dummy + self.cls = CellMeasureMetadata def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.__eq__.__doc__, CellMeasureMetadata.__eq__.__doc__, + BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__, ) def test_lenient_service(self): - qualname___eq__ = qualname(CellMeasureMetadata.__eq__) + qualname___eq__ = qualname(self.cls.__eq__) self.assertIn(qualname___eq__, LENIENT) self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[CellMeasureMetadata.__eq__]) + self.assertTrue(LENIENT[self.cls.__eq__]) def test_call(self): other = sentinel.other return_value = sentinel.return_value - metadata = CellMeasureMetadata( - *(None,) * len(CellMeasureMetadata._fields) - ) + metadata = self.cls(*(None,) * len(self.cls._fields)) with mock.patch.object( BaseMetadata, "__eq__", return_value=return_value ) as mocker: @@ -108,96 +108,96 @@ def test_call(self): self.assertEqual(dict(), kwargs) def test_op_lenient_same(self): - lmetadata = CellMeasureMetadata(**self.values) - rmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_lenient_same_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_lenient_same_measure_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_lenient_different(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_lenient_different_measure(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_same(self): - lmetadata = CellMeasureMetadata(**self.values) - rmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_strict_different(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_different_measure(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_different_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_different_measure_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) @@ -215,20 +215,19 @@ def setUp(self): measure=sentinel.measure, ) self.dummy = sentinel.dummy - self.none = CellMeasureMetadata( - *(None,) * len(CellMeasureMetadata._fields) - ) + self.cls = CellMeasureMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.combine.__doc__, CellMeasureMetadata.combine.__doc__, + BaseMetadata.combine.__doc__, self.cls.combine.__doc__, ) def test_lenient_service(self): - qualname_combine = qualname(CellMeasureMetadata.combine) + qualname_combine = qualname(self.cls.combine) self.assertIn(qualname_combine, LENIENT) self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[CellMeasureMetadata.combine]) + self.assertTrue(LENIENT[self.cls.combine]) def test_lenient_default(self): other = sentinel.other @@ -260,8 +259,8 @@ def test_lenient(self): self.assertEqual(dict(lenient=lenient), kwargs) def test_op_lenient_same(self): - lmetadata = CellMeasureMetadata(**self.values) - rmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = self.values with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -269,10 +268,10 @@ def test_op_lenient_same(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_same_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = self.values with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -280,10 +279,10 @@ def test_op_lenient_same_none(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_same_measure_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = right.copy() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -291,10 +290,10 @@ def test_op_lenient_same_measure_none(self): self.assertTrue(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_different(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["units"] = None @@ -303,10 +302,10 @@ def test_op_lenient_different(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_different_measure(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["measure"] = None @@ -315,8 +314,8 @@ def test_op_lenient_different_measure(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_same(self): - lmetadata = CellMeasureMetadata(**self.values) - rmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = self.values.copy() with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -324,10 +323,10 @@ def test_op_strict_same(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["long_name"] = None @@ -336,10 +335,10 @@ def test_op_strict_different(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different_measure(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["measure"] = None @@ -348,10 +347,10 @@ def test_op_strict_different_measure(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["long_name"] = None @@ -360,10 +359,10 @@ def test_op_strict_different_none(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different_measure_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["measure"] = None @@ -383,21 +382,19 @@ def setUp(self): measure=sentinel.measure, ) self.dummy = sentinel.dummy - self.none = CellMeasureMetadata( - *(None,) * len(CellMeasureMetadata._fields) - ) + self.cls = CellMeasureMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.difference.__doc__, - CellMeasureMetadata.difference.__doc__, + BaseMetadata.difference.__doc__, self.cls.difference.__doc__, ) def test_lenient_service(self): - qualname_difference = qualname(CellMeasureMetadata.difference) + qualname_difference = qualname(self.cls.difference) self.assertIn(qualname_difference, LENIENT) self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[CellMeasureMetadata.difference]) + self.assertTrue(LENIENT[self.cls.difference]) def test_lenient_default(self): other = sentinel.other @@ -429,8 +426,8 @@ def test_lenient(self): self.assertEqual(dict(lenient=lenient), kwargs) def test_op_lenient_same(self): - lmetadata = CellMeasureMetadata(**self.values) - rmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -442,10 +439,10 @@ def test_op_lenient_same(self): ) def test_op_lenient_same_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -457,10 +454,10 @@ def test_op_lenient_same_none(self): ) def test_op_lenient_same_measure_none(self): - lmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["measure"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["measure"] = (sentinel.measure, None) rexpected = deepcopy(self.none)._asdict() @@ -476,10 +473,10 @@ def test_op_lenient_same_measure_none(self): def test_op_lenient_different(self): left = self.values.copy() - lmetadata = CellMeasureMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["units"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["units"] = (left["units"], right["units"]) rexpected = deepcopy(self.none)._asdict() @@ -495,10 +492,10 @@ def test_op_lenient_different(self): def test_op_lenient_different_measure(self): left = self.values.copy() - lmetadata = CellMeasureMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["measure"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["measure"] = (left["measure"], right["measure"]) rexpected = deepcopy(self.none)._asdict() @@ -513,8 +510,8 @@ def test_op_lenient_different_measure(self): ) def test_op_strict_same(self): - lmetadata = CellMeasureMetadata(**self.values) - rmetadata = CellMeasureMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -527,10 +524,10 @@ def test_op_strict_same(self): def test_op_strict_different(self): left = self.values.copy() - lmetadata = CellMeasureMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) rexpected = deepcopy(self.none)._asdict() @@ -546,10 +543,10 @@ def test_op_strict_different(self): def test_op_strict_different_measure(self): left = self.values.copy() - lmetadata = CellMeasureMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["measure"] = self.dummy - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["measure"] = (left["measure"], right["measure"]) rexpected = deepcopy(self.none)._asdict() @@ -565,10 +562,10 @@ def test_op_strict_different_measure(self): def test_op_strict_different_none(self): left = self.values.copy() - lmetadata = CellMeasureMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["long_name"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) rexpected = deepcopy(self.none)._asdict() @@ -584,10 +581,10 @@ def test_op_strict_different_none(self): def test_op_strict_different_measure_none(self): left = self.values.copy() - lmetadata = CellMeasureMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["measure"] = None - rmetadata = CellMeasureMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["measure"] = (left["measure"], right["measure"]) rexpected = deepcopy(self.none)._asdict() @@ -604,20 +601,17 @@ def test_op_strict_different_measure_none(self): class Test_equal(tests.IrisTest): def setUp(self): - self.none = CellMeasureMetadata( - *(None,) * len(CellMeasureMetadata._fields) - ) + self.cls = CellMeasureMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.equal.__doc__, CellMeasureMetadata.equal.__doc__ - ) + self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) def test_lenient_service(self): - qualname_equal = qualname(CellMeasureMetadata.equal) + qualname_equal = qualname(self.cls.equal) self.assertIn(qualname_equal, LENIENT) self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue(LENIENT[CellMeasureMetadata.equal]) + self.assertTrue(LENIENT[self.cls.equal]) def test_lenient_default(self): other = sentinel.other diff --git a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py index 0aefc066d6..863e599f03 100644 --- a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py @@ -29,9 +29,10 @@ def setUp(self): self.attributes = mock.sentinel.attributes self.coord_system = mock.sentinel.coord_system self.climatological = mock.sentinel.climatological + self.cls = CoordMetadata def test_repr(self): - metadata = CoordMetadata( + metadata = self.cls( standard_name=self.standard_name, long_name=self.long_name, var_name=self.var_name, @@ -66,10 +67,10 @@ def test__fields(self): "coord_system", "climatological", ) - self.assertEqual(CoordMetadata._fields, expected) + self.assertEqual(self.cls._fields, expected) def test_bases(self): - self.assertTrue(issubclass(CoordMetadata, BaseMetadata)) + self.assertTrue(issubclass(self.cls, BaseMetadata)) class Test___eq__(tests.IrisTest): @@ -84,22 +85,23 @@ def setUp(self): climatological=sentinel.climatological, ) self.dummy = sentinel.dummy + self.cls = CoordMetadata def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.__eq__.__doc__, CoordMetadata.__eq__.__doc__, + BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__, ) def test_lenient_service(self): - qualname___eq__ = qualname(CoordMetadata.__eq__) + qualname___eq__ = qualname(self.cls.__eq__) self.assertIn(qualname___eq__, LENIENT) self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[CoordMetadata.__eq__]) + self.assertTrue(LENIENT[self.cls.__eq__]) def test_call(self): other = sentinel.other return_value = sentinel.return_value - metadata = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + metadata = self.cls(*(None,) * len(self.cls._fields)) with mock.patch.object( BaseMetadata, "__eq__", return_value=return_value ) as mocker: @@ -112,79 +114,79 @@ def test_call(self): self.assertEqual(dict(), kwargs) def test_op_lenient_same(self): - lmetadata = CoordMetadata(**self.values) - rmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_lenient_same_none(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_lenient_same_members_none(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_lenient_different(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_lenient_different_members(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_same(self): - lmetadata = CoordMetadata(**self.values) - rmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) def test_op_strict_different(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_different_members(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch( "iris.common.metadata.LENIENT", return_value=False @@ -193,21 +195,21 @@ def test_op_strict_different_members(self): self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_different_none(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch("iris.common.metadata.LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) def test_op_strict_different_members_none(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) with mock.patch( "iris.common.metadata.LENIENT", return_value=False @@ -228,18 +230,19 @@ def setUp(self): climatological=sentinel.climatological, ) self.dummy = sentinel.dummy - self.none = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + self.cls = CoordMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.combine.__doc__, CoordMetadata.combine.__doc__, + BaseMetadata.combine.__doc__, self.cls.combine.__doc__, ) def test_lenient_service(self): - qualname_combine = qualname(CoordMetadata.combine) + qualname_combine = qualname(self.cls.combine) self.assertIn(qualname_combine, LENIENT) self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[CoordMetadata.combine]) + self.assertTrue(LENIENT[self.cls.combine]) def test_lenient_default(self): other = sentinel.other @@ -271,8 +274,8 @@ def test_lenient(self): self.assertEqual(dict(lenient=lenient), kwargs) def test_op_lenient_same(self): - lmetadata = CoordMetadata(**self.values) - rmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = self.values with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -280,10 +283,10 @@ def test_op_lenient_same(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_same_none(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = self.values with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -291,11 +294,11 @@ def test_op_lenient_same_none(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_same_members_none(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = right.copy() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -307,10 +310,10 @@ def test_op_lenient_same_members_none(self): ) def test_op_lenient_different(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["units"] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["units"] = None @@ -319,11 +322,11 @@ def test_op_lenient_different(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_lenient_different_members(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected[member] = None @@ -336,8 +339,8 @@ def test_op_lenient_different_members(self): ) def test_op_strict_same(self): - lmetadata = CoordMetadata(**self.values) - rmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = self.values.copy() with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -345,10 +348,10 @@ def test_op_strict_same(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["long_name"] = None @@ -357,11 +360,11 @@ def test_op_strict_different(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different_members(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected[member] = None @@ -376,10 +379,10 @@ def test_op_strict_different_members(self): ) def test_op_strict_different_none(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["long_name"] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected["long_name"] = None @@ -388,11 +391,11 @@ def test_op_strict_different_none(self): self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) def test_op_strict_different_members_none(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) right = self.values.copy() right[member] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = self.values.copy() expected[member] = None @@ -419,18 +422,19 @@ def setUp(self): climatological=sentinel.climatological, ) self.dummy = sentinel.dummy - self.none = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + self.cls = CoordMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): self.assertEqual( - BaseMetadata.difference.__doc__, CoordMetadata.difference.__doc__, + BaseMetadata.difference.__doc__, self.cls.difference.__doc__, ) def test_lenient_service(self): - qualname_difference = qualname(CoordMetadata.difference) + qualname_difference = qualname(self.cls.difference) self.assertIn(qualname_difference, LENIENT) self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[CoordMetadata.difference]) + self.assertTrue(LENIENT[self.cls.difference]) def test_lenient_default(self): other = sentinel.other @@ -462,8 +466,8 @@ def test_lenient(self): self.assertEqual(dict(lenient=lenient), kwargs) def test_op_lenient_same(self): - lmetadata = CoordMetadata(**self.values) - rmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -475,10 +479,10 @@ def test_op_lenient_same(self): ) def test_op_lenient_same_none(self): - lmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): @@ -490,12 +494,12 @@ def test_op_lenient_same_none(self): ) def test_op_lenient_same_members_none(self): - for member in CoordMetadata._members: - lmetadata = CoordMetadata(**self.values) + for member in self.cls._members: + lmetadata = self.cls(**self.values) member_value = getattr(lmetadata, member) right = self.values.copy() right[member] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected[member] = (member_value, None) rexpected = deepcopy(self.none)._asdict() @@ -511,10 +515,10 @@ def test_op_lenient_same_members_none(self): def test_op_lenient_different(self): left = self.values.copy() - lmetadata = CoordMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["units"] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["units"] = (left["units"], right["units"]) rexpected = deepcopy(self.none)._asdict() @@ -529,12 +533,12 @@ def test_op_lenient_different(self): ) def test_op_lenient_different_members(self): - for member in CoordMetadata._members: + for member in self.cls._members: left = self.values.copy() - lmetadata = CoordMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right[member] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected[member] = (left[member], right[member]) rexpected = deepcopy(self.none)._asdict() @@ -549,8 +553,8 @@ def test_op_lenient_different_members(self): ) def test_op_strict_same(self): - lmetadata = CoordMetadata(**self.values) - rmetadata = CoordMetadata(**self.values) + lmetadata = self.cls(**self.values) + rmetadata = self.cls(**self.values) expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): @@ -563,10 +567,10 @@ def test_op_strict_same(self): def test_op_strict_different(self): left = self.values.copy() - lmetadata = CoordMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["long_name"] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) rexpected = deepcopy(self.none)._asdict() @@ -581,12 +585,12 @@ def test_op_strict_different(self): ) def test_op_strict_different_members(self): - for member in CoordMetadata._members: + for member in self.cls._members: left = self.values.copy() - lmetadata = CoordMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right[member] = self.dummy - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected[member] = (left[member], right[member]) rexpected = deepcopy(self.none)._asdict() @@ -604,10 +608,10 @@ def test_op_strict_different_members(self): def test_op_strict_different_none(self): left = self.values.copy() - lmetadata = CoordMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right["long_name"] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected["long_name"] = (left["long_name"], right["long_name"]) rexpected = deepcopy(self.none)._asdict() @@ -622,12 +626,12 @@ def test_op_strict_different_none(self): ) def test_op_strict_different_members_none(self): - for member in CoordMetadata._members: + for member in self.cls._members: left = self.values.copy() - lmetadata = CoordMetadata(**left) + lmetadata = self.cls(**left) right = self.values.copy() right[member] = None - rmetadata = CoordMetadata(**right) + rmetadata = self.cls(**right) lexpected = deepcopy(self.none)._asdict() lexpected[member] = (left[member], right[member]) rexpected = deepcopy(self.none)._asdict() @@ -646,18 +650,17 @@ def test_op_strict_different_members_none(self): class Test_equal(tests.IrisTest): def setUp(self): - self.none = CoordMetadata(*(None,) * len(CoordMetadata._fields)) + self.cls = CoordMetadata + self.none = self.cls(*(None,) * len(self.cls._fields)) def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.equal.__doc__, CoordMetadata.equal.__doc__ - ) + self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) def test_lenient_service(self): - qualname_equal = qualname(CoordMetadata.equal) + qualname_equal = qualname(self.cls.equal) self.assertIn(qualname_equal, LENIENT) self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue(LENIENT[CoordMetadata.equal]) + self.assertTrue(LENIENT[self.cls.equal]) def test_lenient_default(self): other = sentinel.other diff --git a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py index c22b019f66..dcb7b424e1 100644 --- a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py @@ -51,9 +51,10 @@ def setUp(self): self.units = mock.sentinel.units self.attributes = mock.sentinel.attributes self.cell_methods = mock.sentinel.cell_methods + self.cls = CubeMetadata def test_repr(self): - metadata = CubeMetadata( + metadata = self.cls( standard_name=self.standard_name, long_name=self.long_name, var_name=self.var_name, @@ -84,10 +85,10 @@ def test__fields(self): "attributes", "cell_methods", ) - self.assertEqual(CubeMetadata._fields, expected) + self.assertEqual(self.cls._fields, expected) def test_bases(self): - self.assertTrue(issubclass(CubeMetadata, BaseMetadata)) + self.assertTrue(issubclass(self.cls, BaseMetadata)) class Test___eq__(tests.IrisTest): From 638ece17eb8e5c6d031eb98fe66ec0217024980c Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 22 Jun 2020 23:08:00 +0100 Subject: [PATCH 29/45] fix typo --- lib/iris/common/metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index b00850f670..a391104248 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -841,7 +841,7 @@ def _combine_lenient(self, other): Args: - * other (CubeMeatadata): + * other (CubeMetadata): The other cube metadata participating in the lenient combination. Returns: From 14c9d147eca109d4c0ba9ccff9b0cdc57573ee11 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 22 Jun 2020 23:22:57 +0100 Subject: [PATCH 30/45] fix context manager ephemeral services --- lib/iris/common/lenient.py | 34 +++++++++++++++++-- .../tests/unit/common/lenient/test_Lenient.py | 6 ++-- .../common/lenient/test_lenient_client.py | 2 +- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 9776ef318f..20e5634694 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -353,14 +353,29 @@ def context(self, *args, **kwargs): provided only as an example. """ + + def update_client(client, services): + if client in self.__dict__: + existing_services = self.__dict__[client] + else: + existing_services = () + + self.__dict__[client] = tuple(set(existing_services + services)) + # Save the original state. original_state = deepcopy(self.__dict__) + # Temporarily update the state with the kwargs first. for name, value in kwargs.items(): self[name] = value - # Temporarily update the client/services, if provided. + + # Get the active client. + active = self.__dict__["active"] + if args: - active = self.__dict__["active"] + # Update the client with the provided services. + new_services = tuple([qualname(arg) for arg in args]) + if active is None: # Ensure not to use "context" as the ephemeral name # of the context manager runtime "active" lenient client, @@ -368,7 +383,20 @@ def context(self, *args, **kwargs): # i.e., Lenient.context, via Lenient.__getattr__ active = "__context" self.__dict__["active"] = active - self.__dict__[active] = tuple([qualname(arg) for arg in args]) + self.__dict__[active] = new_services + else: + # Append provided services to any pre-existing services of the active client. + update_client(active, new_services) + else: + # Append previous ephemeral services (for non-specific client) to the active client. + if ( + active is not None + and active != "__context" + and "__context" in self.__dict__ + ): + new_services = self.__dict__["__context"] + update_client(active, new_services) + try: yield finally: diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index be881c7115..5c721dab51 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -548,7 +548,8 @@ def test_args_str(self): self.assertEqual(pre, self.default) expected = self.default.copy() expected.update(dict(active=client, client=services)) - self.assertEqual(context, expected) + self.assertEqual(context["active"], expected["active"]) + self.assertEqual(set(context["client"]), set(expected["client"])) self.assertEqual(post, self.default) def test_args_callable(self): @@ -568,7 +569,8 @@ def service2(): self.assertEqual(pre, self.default) expected = self.default.copy() expected.update(dict(active=client, client=qualname_services)) - self.assertEqual(context, expected) + self.assertEqual(context["active"], expected["active"]) + self.assertEqual(set(context["client"]), set(expected["client"])) self.assertEqual(post, self.default) def test_context_runtime(self): diff --git a/lib/iris/tests/unit/common/lenient/test_lenient_client.py b/lib/iris/tests/unit/common/lenient/test_lenient_client.py index 20d0b47e23..45370a0dd7 100644 --- a/lib/iris/tests/unit/common/lenient/test_lenient_client.py +++ b/lib/iris/tests/unit/common/lenient/test_lenient_client.py @@ -159,7 +159,7 @@ def myclient(): qualname_client = self.client.format("test_call_kwargs_iterable") self.assertEqual(result[self.active], qualname_client) self.assertIn(qualname_client, result) - self.assertEqual(result[qualname_client], services) + self.assertEqual(set(result[qualname_client]), set(services)) def test_call_client_args_kwargs(self): @lenient_client() From bce4c859dc3d28f1e31a01edee2ebbfaf9db0f73 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 23 Jun 2020 02:25:29 +0100 Subject: [PATCH 31/45] add logging --- lib/iris/common/metadata.py | 17 ++++++++++++----- lib/iris/config.py | 11 +++++++++++ lib/iris/etc/logging.yaml | 29 +++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 lib/iris/etc/logging.yaml diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index a391104248..e8eca5b462 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -8,6 +8,7 @@ from collections import namedtuple from collections.abc import Iterable, Mapping from functools import wraps +import logging import re from .lenient import LENIENT, lenient_service, qualname @@ -30,6 +31,9 @@ # https://www.unidata.ucar.edu/software/netcdf/docs/netcdf_data_set_components.html#object_name _TOKEN_PARSE = re.compile(r"""^[a-zA-Z0-9][\w\.\+\-@]*$""") +# Configure the logger. +logger = logging.getLogger(__name__) + class _NamedTupleMeta(ABCMeta): """ @@ -112,10 +116,13 @@ def __eq__(self, other): if hasattr(other, "__class__") and other.__class__ is self.__class__: if LENIENT(self.__eq__) or LENIENT(self.equal): # Perform "lenient" equality. - print("lenient __eq__") + logger.debug( + "lenient", extra=dict(cls=self.__class__.__name__) + ) result = self._compare_lenient(other) else: # Perform "strict" equality. + logger.debug("strict", extra=dict(cls=self.__class__.__name__)) result = super().__eq__(other) return result @@ -203,11 +210,11 @@ def _combine(self, other): """Perform associated metadata member combination.""" if LENIENT(self.combine): # Perform "lenient" combine. - print("lenient combine") + logger.debug("lenient", extra=dict(cls=self.__class__.__name__)) values = self._combine_lenient(other) else: # Perform "strict" combine. - print("strict combine") + logger.debug("strict", extra=dict(cls=self.__class__.__name__)) def func(field): value = getattr(self, field) @@ -331,11 +338,11 @@ def _difference(self, other): """Perform associated metadata member difference.""" if LENIENT(self.difference): # Perform "lenient" difference. - print("lenient difference") + logger.debug("lenient", extra=dict(cls=self.__class__.__name__)) values = self._difference_lenient(other) else: # Perform "strict" difference. - print("strict difference") + logger.debug("strict", extra=dict(cls=self.__class__.__name__)) def func(field): left = getattr(self, field) diff --git a/lib/iris/config.py b/lib/iris/config.py index e1d7dee29d..eeef1873f9 100644 --- a/lib/iris/config.py +++ b/lib/iris/config.py @@ -32,8 +32,11 @@ import configparser import contextlib +import logging.config import os.path +import pathlib import warnings +import yaml # Returns simple string options @@ -81,6 +84,14 @@ def get_dir_option(section, option, default=None): config = configparser.ConfigParser() config.read([os.path.join(CONFIG_PATH, "site.cfg")]) +# Configure logging. +fname_logging = pathlib.Path(CONFIG_PATH) / "logging.yaml" +if not fname_logging.exists(): + emsg = f"Logging configuration file '{fname_logging!s}' does not exist." + raise FileNotFoundError(emsg) +with open(fname_logging) as fi: + logging.config.dictConfig(yaml.safe_load(fi)) +del fname_logging ################## # Resource options diff --git a/lib/iris/etc/logging.yaml b/lib/iris/etc/logging.yaml new file mode 100644 index 0000000000..c651de95b8 --- /dev/null +++ b/lib/iris/etc/logging.yaml @@ -0,0 +1,29 @@ +version: 1 + +formatters: + basic: + format: "%(asctime)s %(name)s %(levelname)s - %(message)s" + datefmt: "%d-%m-%Y %H:%M:%S" + basic-func: + format: "%(asctime)s %(name)s %(levelname)s - %(message)s [%(cls)s.%(funcName)s]" + datefmt: "%d-%m-%Y %H:%M:%S" + +handlers: + console: + class: logging.StreamHandler + formatter: basic + stream: ext://sys.stdout + console-func: + class: logging.StreamHandler + formatter: basic-func + stream: ext://sys.stdout + +loggers: + iris.common.metadata: + level: INFO + handlers: [console-func] + propagate: no + +root: + level: INFO + handlers: [console] From e70d85b819824ea51fa86db891cdac0fb90e7eed Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 9 Jan 2020 12:42:23 +0000 Subject: [PATCH 32/45] Pin pillow to make graphics tests work again. (#3630) --- requirements/test.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/test.txt b/requirements/test.txt index 89358f7f76..0a0ebb1b8f 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,6 +3,7 @@ black==19.10b0 #conda: black=19.10b0 filelock +pillow<7 imagehash>=4.0 nose pre-commit From afaa96193005ad17e2d3ce580e4652abad567071 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Tue, 11 Feb 2020 12:10:36 +0000 Subject: [PATCH 33/45] Fixed tests since Numpy 1.18 deprecation of non-int num arguments for linspace. (#3655) --- .../test_regrid_area_weighted_rectilinear_src_and_grid.py | 3 ++- lib/iris/tests/unit/analysis/cartography/test_project.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/iris/tests/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py b/lib/iris/tests/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py index 97127cb0b4..20f4a45264 100644 --- a/lib/iris/tests/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py +++ b/lib/iris/tests/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py @@ -108,8 +108,9 @@ def _resampled_coord(coord, samplefactor): delta = 0.00001 * np.sign(upper - lower) * abs(bounds[0, 1] - bounds[0, 0]) lower = lower + delta upper = upper - delta + samples = int(len(bounds) * samplefactor) new_points, step = np.linspace( - lower, upper, len(bounds) * samplefactor, endpoint=False, retstep=True + lower, upper, samples, endpoint=False, retstep=True ) new_points += step * 0.5 new_coord = coord.copy(points=new_points) diff --git a/lib/iris/tests/unit/analysis/cartography/test_project.py b/lib/iris/tests/unit/analysis/cartography/test_project.py index d617e33898..33edfb5675 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_project.py +++ b/lib/iris/tests/unit/analysis/cartography/test_project.py @@ -81,7 +81,7 @@ def test_bad_resolution_negative(self): @tests.skip_data def test_bad_resolution_non_numeric(self): cube = low_res_4d() - with self.assertRaises(ValueError): + with self.assertRaises(TypeError): project(cube, ROBINSON, nx=200, ny="abc") @tests.skip_data From 91ff5c984b34870af93197ece69eb72d01a8defd Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Tue, 17 Mar 2020 16:45:49 +0000 Subject: [PATCH 34/45] Switched use of datetime.weekday() to datetime.dayofwk. (#3687) --- lib/iris/coord_categorisation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/iris/coord_categorisation.py b/lib/iris/coord_categorisation.py index 838874b5f9..d299cbeaa4 100644 --- a/lib/iris/coord_categorisation.py +++ b/lib/iris/coord_categorisation.py @@ -182,7 +182,7 @@ def add_day_of_year(cube, coord, name="day_of_year"): def add_weekday_number(cube, coord, name="weekday_number"): """Add a categorical weekday coordinate, values 0..6 [0=Monday].""" add_categorised_coord( - cube, name, coord, lambda coord, x: _pt_date(coord, x).weekday() + cube, name, coord, lambda coord, x: _pt_date(coord, x).dayofwk ) @@ -192,7 +192,7 @@ def add_weekday_fullname(cube, coord, name="weekday_fullname"): cube, name, coord, - lambda coord, x: calendar.day_name[_pt_date(coord, x).weekday()], + lambda coord, x: calendar.day_name[_pt_date(coord, x).dayofwk], units="no_unit", ) @@ -203,7 +203,7 @@ def add_weekday(cube, coord, name="weekday"): cube, name, coord, - lambda coord, x: calendar.day_abbr[_pt_date(coord, x).weekday()], + lambda coord, x: calendar.day_abbr[_pt_date(coord, x).dayofwk], units="no_unit", ) From 944d0ef74ab22581c7784aadd110f40b0e925614 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 18 Mar 2020 16:02:12 +0000 Subject: [PATCH 35/45] New image hashes for mpl 3x2 (#3682) * New image hash for iris.test.test_plot.TestSymbols.test_cloud_cover with matplotlib 3.2.0. * Further images changes for mpl3x2. * Yet more updated image results. --- lib/iris/tests/results/imagerepo.json | 60 ++++++++++++++++++--------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index e22b147de6..884e2b875f 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -141,7 +141,8 @@ "example_tests.test_rotated_pole_mapping.TestRotatedPoleMapping.test_rotated_pole_mapping.2": [ "https://scitools.github.io/test-iris-imagehash/images/v4/ba1e605ec7a191a1b85e9e81c4da58909996b37e3a65e16f7b817939e57a1e01.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ba1e605ec7a193a1b85e9e81c4da58909996b3763a65e16f7b816939ed7a1e01.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/e85a697e97a18681c6da9f8190bf3e263624c1ef3b48c17a2b223c47c0ff3f81.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/e85a697e97a18681c6da9f8190bf3e263624c1ef3b48c17a2b223c47c0ff3f81.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/ea57685f95a886a1c0de9da090be3e2497e1c0ef3f01c17e6b366c17c07b3f01.png" ], "example_tests.test_rotated_pole_mapping.TestRotatedPoleMapping.test_rotated_pole_mapping.3": [ "https://scitools.github.io/test-iris-imagehash/images/v4/fa8172d0847ecd2bc913939c36846c714933799cc3cc8727e67639f939996a58.png", @@ -326,7 +327,8 @@ "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_cube_coord.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/83fec1ff7f90987720029f1ef458cd43811cdb60d647de609485ddb899215f62.png", "https://scitools.github.io/test-iris-imagehash/images/v4/83fec1ff7f94987720009f1ef458cd43810cdb60d647de609485ddb89921df62.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/a3ffc1de7e009c7030019786f438cde3810fd97c93734a778ce07c9f99b02731.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/a3ffc1de7e009c7030019786f438cde3810fd97c93734a778ce07c9f99b02731.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/a3ffc1de7e009c7030019786f438cde3810fd93c9b734a778ce47c9799b02731.png" ], "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_cube_cube.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/83ffc8967e0098a6241f9d26e34b8e42f4d20bb4942759e9941f78f8d7867a39.png", @@ -470,12 +472,14 @@ "iris.tests.test_plot.TestHybridHeight.test_orography.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/fa17291f95e895e8645e7a95c17a6eece4b4e1333b01c07e1bb13909914b9ec1.png", "https://scitools.github.io/test-iris-imagehash/images/v4/fa17291f95e895e8645e7a91c17a6ee464f4e1333b01c17e1bb1390d914b9ec1.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/fa817a91957a857ac4fe268cc07f6e846e05d9373b81d17b1b6a1b41c4fa2cc4.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/fa817a91957a857ac4fe268cc07f6e846e05d9373b81d17b1b6a1b41c4fa2cc4.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/fa817a91917a957ac4ff248cc07f6ea466a5c03f3b81c17f1b321b01935b3fc0.png" ], "iris.tests.test_plot.TestHybridHeight.test_orography.1": [ "https://scitools.github.io/test-iris-imagehash/images/v4/bb07314fc4e0c6b4c31e9ee1847939a1c116c15e7b94e57e1ea9391de16e1ac3.png", "https://scitools.github.io/test-iris-imagehash/images/v4/bb07314fc6e1c6b4c31e9ee1846939a1c116c15e7b14e17e1ea9393de16e1ac3.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/af0b690f96f0d2d4c25e94a194ad3da19a52c25e3f02c07f3fa52d03c16a3fcb.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/af0b690f96f0d2d4c25e94a194ad3da19a52c25e3f02c07f3fa52d03c16a3fcb.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/ea07695f95e0d2b4c09d95e0956a3da99294c2be3e85c07f3fa92b05c15e3f42.png" ], "iris.tests.test_plot.TestHybridHeight.test_points.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/ea953bfb956ac4f4649f1a05c56e6ca45a53945e6ea5c13f1b498542c13f1b41.png", @@ -694,12 +698,14 @@ "https://scitools.github.io/test-iris-imagehash/images/v4/aa953d0f85fab50fd0f2956a7a1785fafa176877d00f68f1d02c60f2f008d0f0.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ebeaa5419e94b5019e97950d685395bee05361fad05560fad01570fef001dabe.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ebeaa5419e95b5419e97950d6853953ee053617ad05560fad01570fef001dabe.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/ebfaa56f96a1856cd681a56ee8162d52e8467e12c50c7e8095ad7e0095ad03ff.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/ebfaa56f96a1856cd681a56ee8162d52e8467e12c50c7e8095ad7e0095ad03ff.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/eaa9b5699556854e9456854ed05625f9c0a92bfdc0a90afd81f97e00857e6af6.png" ], "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.5": [ "https://scitools.github.io/test-iris-imagehash/images/v4/ebfaaf439e87b5019687b5019687b56ac05561fae07103fe6079687a607178f8.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ebfa2d4b968795059e87970f6854697ae055697ac08561fad041d7aef001d6ae.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/eb7a3e0c978187a4950190bc6856687a607e687bc0fcc1e394acfc0197fc2bfb.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/eb7a3e0c978187a4950190bc6856687a607e687bc0fcc1e394acfc0197fc2bfb.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/eaf73e0d9503852c950395ac9528c1fad16cc0f2d1ec6af2c0ec6a536a1797f3.png" ], "iris.tests.test_plot.TestPlotCoordinatesGiven.test_x.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/aeb8b5095a87cd60386592d9ec97ad6dd23ca4f6d0797827f0096216c1f878e6.png", @@ -736,12 +742,14 @@ "https://scitools.github.io/test-iris-imagehash/images/v4/ad2f6d2fd2d09295c3c0c7d13c1bc6d23d2c696ce0e53c3ac393dbf6d205c2c0.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ad2f6d2f92d09295c3d0c7d13c1bc6d23d2c696cf0e53c3ac2b3d9f6d201c2c4.png", "https://scitools.github.io/test-iris-imagehash/images/v4/e85e3e2f97a1c19996a1c8f26c1e360f684a3c2c6913dca497b9d38097a903ff.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/e85e3e3f96a1c3e197a169f1785e3b0e68523e1c398bc58687b1d86096e1039f.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/e85e3e3f96a1c3e197a169f1785e3b0e68523e1c398bc58687b1d86096e1039f.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/ea153e0395aac1f895eac0f8940e69e56a743e5f7a432787691ef860c3c1938f.png" ], "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.5": [ "https://scitools.github.io/test-iris-imagehash/images/v4/e9686d8c9696924797879e3b86929e58696d69cc6869659379626133398d9ccd.png", "https://scitools.github.io/test-iris-imagehash/images/v4/e961658f961e92469e1e1c7966f36cd86165618c70e166b39b9698719e1e9ec8.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/e1a530e29e5ecf199a5acd8f64f1326161a530e265999cd29e52cf199a5e6669.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/e1a530e29e5ecf199a5acd8f64f1326161a530e265999cd29e52cf199a5e6669.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/e96930749696cb9d9697cdc39692671b696c306969eb3c76697319942a0d8699.png" ], "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/bf803f00c05fc4bfc07ec15dc05fd8bbc07cc96c333a32113bd02dd27ced3ec0.png", @@ -765,12 +773,14 @@ "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.4": [ "https://scitools.github.io/test-iris-imagehash/images/v4/ee953f0591ea3f07914a95fa7e07d1fa68156a15d07c6a3dd038c0fef000d0fa.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ae953f0591ea3f07914a95fa7e07d1fa68156a15d07c6a7dd068c0fef000d0fa.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/bec11ab5c1be857ac13e7ae53c422d423e017a85b542fc00c1fefe0091fe03ff.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/bec11ab5c1be857ac13e7ae53c422d423e017a85b542fc00c1fefe0091fe03ff.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/bec13a81c13ec56ac13e5afdd11e256a3e412afd3e4002ff2ee0fe0035fa817a.png" ], "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.5": [ "https://scitools.github.io/test-iris-imagehash/images/v4/e87a973d96a56953968769439685a54ae05117eae0511fba60513bba69717aba.png", "https://scitools.github.io/test-iris-imagehash/images/v4/e87a952d96a56953968769439685a54ae85197eae0511fba60513bba69717aba.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/e85a96ac97a16c5897a1791e95a53b0b913c6953687c4ec3685cc6c36e7c87c3.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/e85a96ac97a16c5897a1791e95a53b0b913c6953687c4ec3685cc6c36e7c87c3.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/ea1595ec95ea681d95ea7b0595ab3b13950d7a536a1cc6f26a0cc4f26e0c85f2.png" ], "iris.tests.test_plot.TestPlotDimAndAuxCoordsKwarg.test_coord_names.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/f9789b388786678686966c9093879ce592c79bc94d19929b6939cf66316c672c.png", @@ -842,12 +852,14 @@ ], "iris.tests.test_plot.TestSymbols.test_cloud_cover.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/e95a330c96a5ccf2695a330c96a5ccf2695a330c96b5ccf3694a330c96b5ccf3.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/eb52916494ad6e1b6b5291e494ad6e1b6b5291e494ad6e1b6b5291e494ad6e1b.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/eb52916494ad6e1b6b5291e494ad6e1b6b5291e494ad6e1b6b5291e494ad6e1b.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/eb5291e494ad6e136b5291ec94ad6e136b5291ec94ad6e136b5291ec94ad6e13.png" ], "iris.tests.test_quickplot.TestLabels.test_alignment.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/fa95350f952ad2f0c1f66ac1c55a4af4e550a52b3e05905e1e419e6f937e3b21.png", "https://scitools.github.io/test-iris-imagehash/images/v4/fa95350f952ad3f0c1f66a81e55a4af4e550a52b3e05905e1e419e6f937e1b21.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/be8137f4954ac03fc0ff3e81d03f496a6d00b4af3ea0c07f6fa232c0db7f2d00.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/be8137f4954ac03fc0ff3e81d03f496a6d00b4af3ea0c07f6fa232c0db7f2d00.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/be813fe0954ac07fc0ff3e81c03fc97a6d0094af3f80c17f36a53240d97f2d82.png" ], "iris.tests.test_quickplot.TestLabels.test_contour.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/a3fd956a7a01a5ee321fc96666919b6ec15fdca593600d2586785a259dfa5a01.png", @@ -921,18 +933,21 @@ "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.4": [ "https://scitools.github.io/test-iris-imagehash/images/v4/aa97b70ff5f0970f20b2956a6a17957af805da71d06f5a75d02cd870d800d8f2.png", "https://scitools.github.io/test-iris-imagehash/images/v4/e1faa549de9497090697971d60539f3ef171c87ac075487ad025d87ed801da3e.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/eadab54fd7a1856d90819d6df8169962e946d862802ed8809ded7e809d2d03ff.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/eadab54fd7a1856d90819d6df8169962e946d862802ed8809ded7e809d2d03ff.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/eaa9b549f756854ea0169d6ad5568969d9a909ed80290afdd9e97e008d6e6a96.png" ], "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.5": [ "https://scitools.github.io/test-iris-imagehash/images/v4/e8faad47f784bd0596859d03969f9962c05dc96ee07189fe6870c862687178f8.png", "https://scitools.github.io/test-iris-imagehash/images/v4/a8fa2d4797859585b6959d07605f896ee051697ad061d9fad0619aaed801deae.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/aa5b3c0c978187a4b60199bc605f6976687e6873d07c99e390acdc0391fc2f7b.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/aa5b3c0c978187a4b60199bc605f6976687e6873d07c99e390acdc0391fc2f7b.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/aad73e0df78085acb50195ac8029d9f2d16cd8f2d1ec48f280ec6a536a17b7f3.png" ], "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_x.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/a6ffb5097e84cde2224598d1649f8d6cd2388c76d0799867d009da76c9f8d866.png", "https://scitools.github.io/test-iris-imagehash/images/v4/a6bfb5097f84cde2224599d1649f8d6cd2388c76d0799867d009da76c1f8d866.png", "https://scitools.github.io/test-iris-imagehash/images/v4/a6fbb50cfbd0c036203598dce4c88d26d32f8cf3886e1df3dc047b4289ec6e72.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/a6fb958dfb50c03e203598dca4c9cd26933f9cb3886e1df1dc047b4289ec2e72.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/a6fb958dfb50c03e203598dca4c9cd26933f9cb3886e1df1dc047b4289ec2e72.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/a6fb958dff50c03e203598dca4c9cd26933f9cf3886e1de1dc047b4289ec2672.png" ], "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_y.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/a7ff978b7f00c9c830d7992166179e969509d866c478d964d079c876d869da26.png", @@ -962,12 +977,14 @@ "https://scitools.github.io/test-iris-imagehash/images/v4/ad2f6d2fd2d09295c2d1c3d33c1bc2d67d2c696ce0653c3ac2b1d976da05c2c4.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ad2f6d2fd2d09295c2d1c3d33c1bc2d27d2c696ce0e53c3ad2b1d976da01c2c4.png", "https://scitools.github.io/test-iris-imagehash/images/v4/e85e3e2f97a1c19996a1c8f26d1e3a0f684a3c2c6913dc2497b9db8095e502ff.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/e85e3c1f97a1c3e197a1c9f37c5e390668521e0c390bdd8685b1d86096e5279f.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/e85e3c1f97a1c3e197a1c9f37c5e390668521e0c390bdd8685b1d86096e5279f.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/ea153f0395eac1f895eac9fa941c79e56a741e4f68430f876916f860c9c1938d.png" ], "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.5": [ "https://scitools.github.io/test-iris-imagehash/images/v4/e968658e969692c797879e3b86929e58696d49cd6869c9a37962c923990d9c6d.png", "https://scitools.github.io/test-iris-imagehash/images/v4/e9e1658e961e92569e9e3c7966d36c586165698c70e1ce739b3698619e1e984c.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/e1a530e29e5ecf199a5acd8f64f1326161a538e665a198d29e52cb1d9a5e6669.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/e1a530e29e5ecf199a5acd8f64f1326161a538e665a198d29e52cb1d9a5e6669.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/e96930749696cf9d9697cdc39692670b696c386969eb3866696399a41a0d8e99.png" ], "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/bf813f80c156c05dc0fec29dc17f1a6dd05fc0ff1aa1c57e3b243b20375a1e81.png", @@ -993,19 +1010,22 @@ "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.4": [ "https://scitools.github.io/test-iris-imagehash/images/v4/ae953f87d5e82d86801f91ee6e1591fe7e117876c07d6877d068d878d800d07a.png", "https://scitools.github.io/test-iris-imagehash/images/v4/ae953f87d5e82d87801b91ee6e1599fe7e117874c07d6877d068d878d800d07a.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/bec1329dc5be85dac01d58d73e419d423e41daa59822dc00c5fefe0091fe03ff.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/bec1329dc5be85dac01d58d73e419d423e41daa59822dc00c5fefe0091fe03ff.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/bec13e81c5bec55ac03dd896d17e8d6a1e410af7380008ff1de6fe0099ea237b.png" ], "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.5": [ "https://scitools.github.io/test-iris-imagehash/images/v4/e87a952d96856943969f694696858d4ee0519d6ee07f9b6a78619b2a79711a2a.png", "https://scitools.github.io/test-iris-imagehash/images/v4/e87a952d96856943969f694696858d4ae0519d6ee07f996a78719b2a79711a3a.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/e85e96ac97a168d897a5791695a19927913c3953687ecce3687c86e3487cc6c3.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/e85e96ac97a168d897a5791695a19927913c3953687ecce3687c86e3487cc6c3.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/ea1595ac95e8689d95fb7b0595291963916f3b73487fccf2680484f2486ec6f0.png" ], "iris.tests.test_quickplot.TestTimeReferenceUnitsLabels.test_not_reference_time_units.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/82faa1977fdf89976200ddf6e000d9e7f75f9866d560dae4dc00d966dc005e20.png", "https://scitools.github.io/test-iris-imagehash/images/v4/82b8a1977fdf89876200dde6e000d9e7f77f9866d560dfe4dc00d966fc005e20.png", "https://scitools.github.io/test-iris-imagehash/images/v4/82f8a1977fdf89876200ddf6e000d9e7f77f9866d560dee4dc00d966dc005e20.png", "https://scitools.github.io/test-iris-imagehash/images/v4/82f8a1977fdf89876200dde6e000d9e7f77f9866d560dfe4dc00dd64dc005e20.png", - "https://scitools.github.io/test-iris-imagehash/images/v4/82faa19e7f51898c6001dd86845fd9a2dd7f996281ee19f389ef03ffdc007e00.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/82faa19e7f51898c6001dd86845fd9a2dd7f996281ee19f389ef03ffdc007e00.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/82f8a19e7f51888c6001dda6855fd9a2dd7f986281ee19f389ff03ffdc007e00.png" ], "iris.tests.test_quickplot.TestTimeReferenceUnitsLabels.test_reference_time_units.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/82fe81987fd777ffe0002addd4002805dda8de65dde9d4625bfddc209841de20.png", From f2c7a9f5f7609f1099724bfb462c9f92f804a8dc Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 25 Jun 2020 22:19:25 +0100 Subject: [PATCH 36/45] fix sentinel uniqueness test failure --- lib/iris/tests/unit/common/metadata/test_BaseMetadata.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index c534c3acd4..2515943360 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -13,7 +13,6 @@ import iris.tests as tests from collections import OrderedDict -from copy import deepcopy import unittest.mock as mock from unittest.mock import sentinel @@ -112,7 +111,7 @@ def test_lenient(self): def test_strict_same(self): self.assertTrue(self.metadata.__eq__(self.metadata)) - other = deepcopy(self.metadata) + other = self.cls(**self.kwargs) self.assertTrue(self.metadata.__eq__(other)) self.assertTrue(other.__eq__(self.metadata)) From c0bc36d30ace6854e8b461a6d0be389591c9871a Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 25 Jun 2020 22:38:40 +0100 Subject: [PATCH 37/45] remove redundant cdm mapping test --- lib/iris/tests/test_cdm.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/iris/tests/test_cdm.py b/lib/iris/tests/test_cdm.py index 2c006e0e4f..b036c8d02e 100644 --- a/lib/iris/tests/test_cdm.py +++ b/lib/iris/tests/test_cdm.py @@ -1018,14 +1018,6 @@ def test_metadata_fail(self): (), ) with self.assertRaises(TypeError): - self.t.metadata = { - "standard_name": "air_pressure", - "long_name": "foo", - "var_name": "bar", - "units": "", - "attributes": {"random": "12"}, - } - with self.assertRaises(TypeError): class Metadata: pass From 014b7b087aa16aa795bbfd755876a9329b4bf575 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 1 Jul 2020 08:03:20 +0100 Subject: [PATCH 38/45] difference returns None for no difference --- lib/iris/common/metadata.py | 9 +++++-- .../test_AncillaryVariableMetadata.py | 27 +++++-------------- .../metadata/test_CellMeasureMetadata.py | 27 +++++-------------- .../common/metadata/test_CoordMetadata.py | 27 +++++-------------- .../unit/common/metadata/test_CubeMetadata.py | 27 +++++-------------- 5 files changed, 31 insertions(+), 86 deletions(-) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index e8eca5b462..db22422b8c 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -488,13 +488,18 @@ def difference(self, other, lenient=None): detect whether this lenient operation is enabled. Returns: - Metadata instance. + Metadata instance of member differences or None. """ result = self._api_common( other, self.difference, self._difference, "differ", lenient=lenient ) - return self.__class__(*result) + result = ( + None + if all([item is None for item in result]) + else self.__class__(*result) + ) + return result @lenient_service def equal(self, other, lenient=None): diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index ce9262e44e..9cc8227ee7 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -334,30 +334,20 @@ def test_lenient(self): def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_same_none(self): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_different(self): left = self.values.copy() @@ -381,15 +371,10 @@ def test_op_lenient_different(self): def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_strict_different(self): left = self.values.copy() diff --git a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py index f6afecc559..605aa87fb4 100644 --- a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py @@ -428,30 +428,20 @@ def test_lenient(self): def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_same_none(self): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_same_measure_none(self): lmetadata = self.cls(**self.values) @@ -512,15 +502,10 @@ def test_op_lenient_different_measure(self): def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_strict_different(self): left = self.values.copy() diff --git a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py index 863e599f03..c44a74bc65 100644 --- a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py @@ -468,30 +468,20 @@ def test_lenient(self): def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_same_none(self): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_same_members_none(self): for member in self.cls._members: @@ -555,15 +545,10 @@ def test_op_lenient_different_members(self): def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_strict_different(self): left = self.values.copy() diff --git a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py index dcb7b424e1..46f8f6f3fa 100644 --- a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py @@ -452,30 +452,20 @@ def test_lenient(self): def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_same_none(self): lmetadata = self.cls(**self.values) right = self.values.copy() right["var_name"] = None rmetadata = self.cls(**right) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=True): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_lenient_same_cell_methods_none(self): lmetadata = self.cls(**self.values) @@ -539,15 +529,10 @@ def test_op_lenient_different_cell_methods(self): def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - expected = deepcopy(self.none)._asdict() with mock.patch("iris.common.metadata.LENIENT", return_value=False): - self.assertEqual( - expected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.difference(lmetadata)._asdict() - ) + self.assertIsNone(lmetadata.difference(rmetadata)) + self.assertIsNone(rmetadata.difference(lmetadata)) def test_op_strict_different(self): left = self.values.copy() From 1e6f52e9d9cad8fdd77fb7cc3da19216112bb116 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 1 Jul 2020 09:26:55 +0100 Subject: [PATCH 39/45] protect Lenient and LENIENT private --- lib/iris/common/lenient.py | 22 ++--- lib/iris/common/metadata.py | 10 +- .../{test_Lenient.py => test__Lenient.py} | 38 +++---- .../common/lenient/test_lenient_client.py | 18 ++-- .../common/lenient/test_lenient_service.py | 14 +-- .../test_AncillaryVariableMetadata.py | 62 ++++++------ .../unit/common/metadata/test_BaseMetadata.py | 48 ++++----- .../metadata/test_CellMeasureMetadata.py | 86 ++++++++-------- .../common/metadata/test_CoordMetadata.py | 98 +++++++++++-------- .../unit/common/metadata/test_CubeMetadata.py | 86 ++++++++-------- 10 files changed, 246 insertions(+), 236 deletions(-) rename lib/iris/tests/unit/common/lenient/{test_Lenient.py => test__Lenient.py} (97%) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 20e5634694..30f0bd7649 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -13,8 +13,6 @@ __all__ = [ - "LENIENT", - "Lenient", "lenient_client", "lenient_service", "qualname", @@ -96,7 +94,7 @@ def lenient_client_inner_naked(*args, **kwargs): as active at runtime before executing it. """ - with LENIENT.context(active=qualname(func)): + with _LENIENT.context(active=qualname(func)): result = func(*args, **kwargs) return result @@ -117,7 +115,7 @@ def lenient_client_inner(*args, **kwargs): as active at runtime before executing it. """ - with LENIENT.context(*services, active=qualname(func)): + with _LENIENT.context(*services, active=qualname(func)): result = func(*args, **kwargs) return result @@ -176,7 +174,7 @@ def func(): # return it unchanged (func,) = dargs - LENIENT.register_service(func) + _LENIENT.register_service(func) # This decorator registers 'func': the func itself is unchanged. result = func @@ -185,7 +183,7 @@ def func(): # The decorator has been called with no arguments. # Return a decorator, to apply to 'func' immediately following. def lenient_service_outer(func): - LENIENT.register_service(func) + _LENIENT.register_service(func) # Decorator registers 'func', but func itself is unchanged. return func @@ -218,7 +216,7 @@ def qualname(func): return result -class Lenient(threading.local): +class _Lenient(threading.local): def __init__(self, *args, **kwargs): """ A container for managing the run-time lenient services and client @@ -239,7 +237,7 @@ def __init__(self, *args, **kwargs): For example:: - Lenient(service1, service2, client1=service1, client2=(service1, service2)) + _Lenient(service1, service2, client1=service1, client2=(service1, service2)) Note that, the values of these options are thread-specific. @@ -345,11 +343,11 @@ def context(self, *args, **kwargs): state is restored. For example:: - with iris.LENIENT.context(example_lenient_flag=False): + with iris._LENIENT.context(example_lenient_flag=False): # ... code that expects some non-lenient behaviour .. note:: - iris.LENIENT.example_lenient_flag does not exist and is + iris._LENIENT.example_lenient_flag does not exist and is provided only as an example. """ @@ -380,7 +378,7 @@ def update_client(client, services): # Ensure not to use "context" as the ephemeral name # of the context manager runtime "active" lenient client, # as this causes a namespace clash with this method - # i.e., Lenient.context, via Lenient.__getattr__ + # i.e., _Lenient.context, via _Lenient.__getattr__ active = "__context" self.__dict__["active"] = active self.__dict__[active] = new_services @@ -549,4 +547,4 @@ def unregister_service(self, func): #: Instance that manages all Iris run-time lenient client and service options. -LENIENT = Lenient() +_LENIENT = _Lenient() diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index db22422b8c..e81af2a95d 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -11,7 +11,7 @@ import logging import re -from .lenient import LENIENT, lenient_service, qualname +from .lenient import _LENIENT, lenient_service, qualname __all__ = [ @@ -114,7 +114,7 @@ def __eq__(self, other): """ result = NotImplemented if hasattr(other, "__class__") and other.__class__ is self.__class__: - if LENIENT(self.__eq__) or LENIENT(self.equal): + if _LENIENT(self.__eq__) or _LENIENT(self.equal): # Perform "lenient" equality. logger.debug( "lenient", extra=dict(cls=self.__class__.__name__) @@ -201,14 +201,14 @@ def _api_common( # is a hashable key. args, kwargs = (), {qualname(func_service): False} - with LENIENT.context(*args, **kwargs): + with _LENIENT.context(*args, **kwargs): result = func_operation(other) return result def _combine(self, other): """Perform associated metadata member combination.""" - if LENIENT(self.combine): + if _LENIENT(self.combine): # Perform "lenient" combine. logger.debug("lenient", extra=dict(cls=self.__class__.__name__)) values = self._combine_lenient(other) @@ -336,7 +336,7 @@ def _compare_lenient_attributes(left, right): def _difference(self, other): """Perform associated metadata member difference.""" - if LENIENT(self.difference): + if _LENIENT(self.difference): # Perform "lenient" difference. logger.debug("lenient", extra=dict(cls=self.__class__.__name__)) values = self._difference_lenient(other) diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test__Lenient.py similarity index 97% rename from lib/iris/tests/unit/common/lenient/test_Lenient.py rename to lib/iris/tests/unit/common/lenient/test__Lenient.py index 5c721dab51..57f54e20ac 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test__Lenient.py @@ -17,7 +17,7 @@ from iris.common.lenient import ( LENIENT_ENABLE_DEFAULT, LENIENT_PROTECTED, - Lenient, + _Lenient, qualname, ) @@ -27,18 +27,18 @@ def setUp(self): self.expected = dict(active=None, enable=LENIENT_ENABLE_DEFAULT) def test_default(self): - lenient = Lenient() + lenient = _Lenient() self.assertEqual(self.expected, lenient.__dict__) def test_args_service_str(self): service = "service1" - lenient = Lenient(service) + lenient = _Lenient(service) self.expected.update(dict(service1=True)) self.assertEqual(self.expected, lenient.__dict__) def test_args_services_str(self): services = ("service1", "service2") - lenient = Lenient(*services) + lenient = _Lenient(*services) self.expected.update(dict(service1=True, service2=True)) self.assertEqual(self.expected, lenient.__dict__) @@ -50,7 +50,7 @@ def service2(): pass services = (service1, service2) - lenient = Lenient(*services) + lenient = _Lenient(*services) self.expected.update( {qualname(service1): True, qualname(service2): True,} ) @@ -58,13 +58,13 @@ def service2(): def test_kwargs_client_str(self): client = dict(client1="service1") - lenient = Lenient(**client) + lenient = _Lenient(**client) self.expected.update(dict(client1=("service1",))) self.assertEqual(self.expected, lenient.__dict__) def test_kwargs_clients_str(self): clients = dict(client1="service1", client2="service2") - lenient = Lenient(**clients) + lenient = _Lenient(**clients) self.expected.update( dict(client1=("service1",), client2=("service2",)) ) @@ -89,7 +89,7 @@ def service2(): qualname_client1: service1, qualname_client2: (service1, service2), } - lenient = Lenient(**clients) + lenient = _Lenient(**clients) self.expected.update( { qualname(client1): (qualname(service1),), @@ -102,7 +102,7 @@ def service2(): class Test___call__(tests.IrisTest): def setUp(self): self.client = "myclient" - self.lenient = Lenient() + self.lenient = _Lenient() def test_missing_service_str(self): self.assertFalse(self.lenient("myservice")) @@ -271,7 +271,7 @@ def test_enable(self): class Test___contains__(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_in(self): self.assertIn("active", self.lenient) @@ -282,7 +282,7 @@ def test_not_in(self): class Test___getattr__(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_in(self): self.assertIsNone(self.lenient.active) @@ -295,7 +295,7 @@ def test_not_in(self): class Test__getitem__(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_in(self): self.assertIsNone(self.lenient["active"]) @@ -325,7 +325,7 @@ def service(): class Test___setitem__(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_not_in(self): emsg = "Invalid .* option, got 'wibble'." @@ -481,7 +481,7 @@ def test_enable_invalid(self): class Test_context(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() self.default = dict(active=None, enable=LENIENT_ENABLE_DEFAULT) def copy(self): @@ -588,7 +588,7 @@ def test_context_runtime(self): class Test_enable(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_getter(self): self.assertEqual(self.lenient.enable, LENIENT_ENABLE_DEFAULT) @@ -606,7 +606,7 @@ def test_setter(self): class Test_register_client(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_not_protected(self): emsg = "Cannot register .* protected non-client" @@ -690,7 +690,7 @@ def test_services_append(self): class Test_register_service(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_str(self): service = "service" @@ -723,7 +723,7 @@ def test_not_protected(self): class Test_unregister_client(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_not_protected(self): emsg = "Cannot unregister .* protected non-client" @@ -772,7 +772,7 @@ def client(): class Test_unregister_service(tests.IrisTest): def setUp(self): - self.lenient = Lenient() + self.lenient = _Lenient() def test_not_protected(self): emsg = "Cannot unregister .* protected non-service" diff --git a/lib/iris/tests/unit/common/lenient/test_lenient_client.py b/lib/iris/tests/unit/common/lenient/test_lenient_client.py index 45370a0dd7..ffca451966 100644 --- a/lib/iris/tests/unit/common/lenient/test_lenient_client.py +++ b/lib/iris/tests/unit/common/lenient/test_lenient_client.py @@ -15,7 +15,7 @@ from inspect import getmodule from unittest.mock import sentinel -from iris.common.lenient import LENIENT, lenient_client +from iris.common.lenient import _LENIENT, lenient_client class Test(tests.IrisTest): @@ -50,7 +50,7 @@ def func(): def test_call_naked(self): @lenient_client def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() result = myclient() self.assertIn(self.active, result) @@ -60,7 +60,7 @@ def myclient(): def test_call_naked_alternative(self): def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() result = lenient_client(myclient)() self.assertIn(self.active, result) @@ -87,7 +87,7 @@ def myclient(): def test_call_no_kwargs(self): @lenient_client() def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() result = myclient() self.assertIn(self.active, result) @@ -97,7 +97,7 @@ def myclient(): def test_call_no_kwargs_alternative(self): def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() result = (lenient_client())(myclient)() self.assertIn(self.active, result) @@ -108,7 +108,7 @@ def myclient(): def test_call_kwargs_none(self): @lenient_client(services=None) def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() result = myclient() self.assertIn(self.active, result) @@ -121,7 +121,7 @@ def test_call_kwargs_single(self): @lenient_client(services=service) def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() result = myclient() self.assertIn(self.active, result) @@ -136,7 +136,7 @@ def myservice(): @lenient_client(services=myservice) def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() test_name = "test_call_kwargs_single_callable" result = myclient() @@ -152,7 +152,7 @@ def test_call_kwargs_iterable(self): @lenient_client(services=services) def myclient(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() result = myclient() self.assertIn(self.active, result) diff --git a/lib/iris/tests/unit/common/lenient/test_lenient_service.py b/lib/iris/tests/unit/common/lenient/test_lenient_service.py index 1124b0d01f..9def7dc9d6 100644 --- a/lib/iris/tests/unit/common/lenient/test_lenient_service.py +++ b/lib/iris/tests/unit/common/lenient/test_lenient_service.py @@ -15,7 +15,7 @@ from inspect import getmodule from unittest.mock import sentinel -from iris.common.lenient import LENIENT, lenient_service +from iris.common.lenient import _LENIENT, lenient_service class Test(tests.IrisTest): @@ -38,10 +38,10 @@ def test_args_not_callable(self): def test_call_naked(self): @lenient_service def myservice(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() qualname_service = self.service.format("test_call_naked") - state = LENIENT.__dict__ + state = _LENIENT.__dict__ self.assertIn(qualname_service, state) self.assertTrue(state[qualname_service]) result = myservice() @@ -50,7 +50,7 @@ def myservice(): def test_call_naked_alternative(self): def myservice(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() qualname_service = self.service.format("test_call_naked_alternative") result = lenient_service(myservice)() @@ -76,10 +76,10 @@ def myservice(): def test_call(self): @lenient_service() def myservice(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() qualname_service = self.service.format("test_call") - state = LENIENT.__dict__ + state = _LENIENT.__dict__ self.assertIn(qualname_service, state) self.assertTrue(state[qualname_service]) result = myservice() @@ -88,7 +88,7 @@ def myservice(): def test_call_alternative(self): def myservice(): - return LENIENT.__dict__.copy() + return _LENIENT.__dict__.copy() qualname_service = self.service.format("test_call_alternative") result = (lenient_service())(myservice)() diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index 9cc8227ee7..0a083038e9 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import LENIENT, qualname +from iris.common.lenient import _LENIENT, qualname from iris.common.metadata import BaseMetadata, AncillaryVariableMetadata @@ -83,9 +83,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname___eq__ = qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, LENIENT) - self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[self.cls.__eq__]) + self.assertIn(qualname___eq__, _LENIENT) + self.assertTrue(_LENIENT[qualname___eq__]) + self.assertTrue(_LENIENT[self.cls.__eq__]) def test_call(self): other = sentinel.other @@ -106,7 +106,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -116,7 +116,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -126,7 +126,7 @@ def test_op_lenient_different(self): right["units"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -134,7 +134,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -144,7 +144,7 @@ def test_op_strict_different(self): right["long_name"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -154,7 +154,7 @@ def test_op_strict_different_none(self): right["long_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -179,9 +179,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_combine = qualname(self.cls.combine) - self.assertIn(qualname_combine, LENIENT) - self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[self.cls.combine]) + self.assertIn(qualname_combine, _LENIENT) + self.assertTrue(_LENIENT[qualname_combine]) + self.assertTrue(_LENIENT[self.cls.combine]) def test_lenient_default(self): other = sentinel.other @@ -217,7 +217,7 @@ def test_op_lenient_same(self): rmetadata = self.cls(**self.values) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -228,7 +228,7 @@ def test_op_lenient_same_none(self): rmetadata = self.cls(**right) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -240,7 +240,7 @@ def test_op_lenient_different(self): expected = self.values.copy() expected["units"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -249,7 +249,7 @@ def test_op_strict_same(self): rmetadata = self.cls(**self.values) expected = self.values.copy() - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -261,7 +261,7 @@ def test_op_strict_different(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -273,7 +273,7 @@ def test_op_strict_different_none(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -298,9 +298,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_difference = qualname(self.cls.difference) - self.assertIn(qualname_difference, LENIENT) - self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[self.cls.difference]) + self.assertIn(qualname_difference, _LENIENT) + self.assertTrue(_LENIENT[qualname_difference]) + self.assertTrue(_LENIENT[self.cls.difference]) def test_lenient_default(self): other = sentinel.other @@ -335,7 +335,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -345,7 +345,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -360,7 +360,7 @@ def test_op_lenient_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -372,7 +372,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -387,7 +387,7 @@ def test_op_strict_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -406,7 +406,7 @@ def test_op_strict_different_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -425,9 +425,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_equal = qualname(self.cls.equal) - self.assertIn(qualname_equal, LENIENT) - self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue(LENIENT[self.cls.equal]) + self.assertIn(qualname_equal, _LENIENT) + self.assertTrue(_LENIENT[qualname_equal]) + self.assertTrue(_LENIENT[self.cls.equal]) def test_lenient_default(self): other = sentinel.other diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 2515943360..f7b62a4ba8 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import LENIENT, qualname +from iris.common.lenient import _LENIENT, qualname from iris.common.metadata import BaseMetadata, CubeMetadata @@ -75,9 +75,9 @@ def setUp(self): def test_lenient_service(self): qualname___eq__ = qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, LENIENT) - self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[self.cls.__eq__]) + self.assertIn(qualname___eq__, _LENIENT) + self.assertTrue(_LENIENT[qualname___eq__]) + self.assertTrue(_LENIENT[self.cls.__eq__]) def test_cannot_compare_non_class(self): result = self.metadata.__eq__(None) @@ -91,7 +91,7 @@ def test_cannot_compare(self): def test_lenient(self): return_value = sentinel.return_value with mock.patch( - "iris.common.metadata.LENIENT", return_value=True + "iris.common.metadata._LENIENT", return_value=True ) as mlenient: with mock.patch.object( self.cls, "_compare_lenient", return_value=return_value @@ -206,7 +206,7 @@ def test_lenient(self): return_value = sentinel._combine_lenient other = sentinel.other with mock.patch( - "iris.common.metadata.LENIENT", return_value=True + "iris.common.metadata._LENIENT", return_value=True ) as mlenient: with mock.patch.object( self.cls, "_combine_lenient", return_value=return_value @@ -231,7 +231,7 @@ def test_strict(self): values["var_name"] = dummy values["attributes"] = dummy other = self.cls(**values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): result = self.metadata._combine(other) expected = [ @@ -665,7 +665,7 @@ def test_lenient(self): return_value = sentinel._difference_lenient other = sentinel.other with mock.patch( - "iris.common.metadata.LENIENT", return_value=True + "iris.common.metadata._LENIENT", return_value=True ) as mlenient: with mock.patch.object( self.cls, "_difference_lenient", return_value=return_value @@ -690,7 +690,7 @@ def test_strict(self): values["units"] = dummy other = self.cls(**values) method = "_difference_strict_attributes" - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): with mock.patch.object( self.cls, method, return_value=None ) as mdifference: @@ -1097,9 +1097,9 @@ def setUp(self): def test_lenient_service(self): qualname_combine = qualname(self.cls.combine) - self.assertIn(qualname_combine, LENIENT) - self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[self.cls.combine]) + self.assertIn(qualname_combine, _LENIENT) + self.assertTrue(_LENIENT[qualname_combine]) + self.assertTrue(_LENIENT[self.cls.combine]) def test_cannot_combine_non_class(self): emsg = "Cannot combine" @@ -1130,7 +1130,7 @@ def test_lenient_true(self): with mock.patch.object( self.cls, "_combine", return_value=return_value ) as mcombine: - with mock.patch.object(LENIENT, "context") as mcontext: + with mock.patch.object(_LENIENT, "context") as mcontext: result = self.metadata.combine(self.metadata, lenient=True) self.assertEqual(1, mcontext.call_count) @@ -1149,7 +1149,7 @@ def test_lenient_false(self): with mock.patch.object( self.cls, "_combine", return_value=return_value ) as mcombine: - with mock.patch.object(LENIENT, "context") as mcontext: + with mock.patch.object(_LENIENT, "context") as mcontext: result = self.metadata.combine(self.metadata, lenient=False) self.assertEqual(1, mcontext.call_count) @@ -1185,9 +1185,9 @@ def setUp(self): def test_lenient_service(self): qualname_difference = qualname(self.cls.difference) - self.assertIn(qualname_difference, LENIENT) - self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[self.cls.difference]) + self.assertIn(qualname_difference, _LENIENT) + self.assertTrue(_LENIENT[qualname_difference]) + self.assertTrue(_LENIENT[self.cls.difference]) def test_cannot_differ_non_class(self): emsg = "Cannot differ" @@ -1218,7 +1218,7 @@ def test_lenient_true(self): with mock.patch.object( self.cls, "_difference", return_value=return_value ) as mdifference: - with mock.patch.object(LENIENT, "context") as mcontext: + with mock.patch.object(_LENIENT, "context") as mcontext: result = self.metadata.difference(self.metadata, lenient=True) self.assertEqual(1, mcontext.call_count) @@ -1237,7 +1237,7 @@ def test_lenient_false(self): with mock.patch.object( self.cls, "_difference", return_value=return_value ) as mdifference: - with mock.patch.object(LENIENT, "context") as mcontext: + with mock.patch.object(_LENIENT, "context") as mcontext: result = self.metadata.difference(self.metadata, lenient=False) self.assertEqual(mcontext.call_count, 1) @@ -1266,9 +1266,9 @@ def setUp(self): def test_lenient_service(self): qualname_equal = qualname(self.cls.equal) - self.assertIn(qualname_equal, LENIENT) - self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue((LENIENT[self.cls.equal])) + self.assertIn(qualname_equal, _LENIENT) + self.assertTrue(_LENIENT[qualname_equal]) + self.assertTrue((_LENIENT[self.cls.equal])) def test_cannot_compare_non_class(self): emsg = "Cannot compare" @@ -1299,7 +1299,7 @@ def test_lenient_true(self): with mock.patch.object( self.cls, "__eq__", return_value=return_value ) as m__eq__: - with mock.patch.object(LENIENT, "context") as mcontext: + with mock.patch.object(_LENIENT, "context") as mcontext: result = self.metadata.equal(self.metadata, lenient=True) self.assertEqual(return_value, result) @@ -1318,7 +1318,7 @@ def test_lenient_false(self): with mock.patch.object( self.cls, "__eq__", return_value=return_value ) as m__eq__: - with mock.patch.object(LENIENT, "context") as mcontext: + with mock.patch.object(_LENIENT, "context") as mcontext: result = self.metadata.equal(self.metadata, lenient=False) self.assertEqual(1, mcontext.call_count) diff --git a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py index 605aa87fb4..5bf8158145 100644 --- a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import LENIENT, qualname +from iris.common.lenient import _LENIENT, qualname from iris.common.metadata import BaseMetadata, CellMeasureMetadata @@ -88,9 +88,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname___eq__ = qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, LENIENT) - self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[self.cls.__eq__]) + self.assertIn(qualname___eq__, _LENIENT) + self.assertTrue(_LENIENT[qualname___eq__]) + self.assertTrue(_LENIENT[self.cls.__eq__]) def test_call(self): other = sentinel.other @@ -111,7 +111,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -121,7 +121,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -131,7 +131,7 @@ def test_op_lenient_same_measure_none(self): right["measure"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -141,7 +141,7 @@ def test_op_lenient_different(self): right["units"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -151,7 +151,7 @@ def test_op_lenient_different_measure(self): right["measure"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -159,7 +159,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -169,7 +169,7 @@ def test_op_strict_different(self): right["long_name"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -179,7 +179,7 @@ def test_op_strict_different_measure(self): right["measure"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -189,7 +189,7 @@ def test_op_strict_different_none(self): right["long_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -199,7 +199,7 @@ def test_op_strict_different_measure_none(self): right["measure"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -225,9 +225,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_combine = qualname(self.cls.combine) - self.assertIn(qualname_combine, LENIENT) - self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[self.cls.combine]) + self.assertIn(qualname_combine, _LENIENT) + self.assertTrue(_LENIENT[qualname_combine]) + self.assertTrue(_LENIENT[self.cls.combine]) def test_lenient_default(self): other = sentinel.other @@ -263,7 +263,7 @@ def test_op_lenient_same(self): rmetadata = self.cls(**self.values) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -274,7 +274,7 @@ def test_op_lenient_same_none(self): rmetadata = self.cls(**right) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -285,7 +285,7 @@ def test_op_lenient_same_measure_none(self): rmetadata = self.cls(**right) expected = right.copy() - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(expected, lmetadata.combine(rmetadata)._asdict()) self.assertTrue(expected, rmetadata.combine(lmetadata)._asdict()) @@ -297,7 +297,7 @@ def test_op_lenient_different(self): expected = self.values.copy() expected["units"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -309,7 +309,7 @@ def test_op_lenient_different_measure(self): expected = self.values.copy() expected["measure"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -318,7 +318,7 @@ def test_op_strict_same(self): rmetadata = self.cls(**self.values) expected = self.values.copy() - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -330,7 +330,7 @@ def test_op_strict_different(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -342,7 +342,7 @@ def test_op_strict_different_measure(self): expected = self.values.copy() expected["measure"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -354,7 +354,7 @@ def test_op_strict_different_none(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -366,7 +366,7 @@ def test_op_strict_different_measure_none(self): expected = self.values.copy() expected["measure"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -392,9 +392,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_difference = qualname(self.cls.difference) - self.assertIn(qualname_difference, LENIENT) - self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[self.cls.difference]) + self.assertIn(qualname_difference, _LENIENT) + self.assertTrue(_LENIENT[qualname_difference]) + self.assertTrue(_LENIENT[self.cls.difference]) def test_lenient_default(self): other = sentinel.other @@ -429,7 +429,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -439,7 +439,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -453,7 +453,7 @@ def test_op_lenient_same_measure_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["measure"] = (None, sentinel.measure) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -472,7 +472,7 @@ def test_op_lenient_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -491,7 +491,7 @@ def test_op_lenient_different_measure(self): rexpected = deepcopy(self.none)._asdict() rexpected["measure"] = lexpected["measure"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -503,7 +503,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -518,7 +518,7 @@ def test_op_strict_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -537,7 +537,7 @@ def test_op_strict_different_measure(self): rexpected = deepcopy(self.none)._asdict() rexpected["measure"] = lexpected["measure"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -556,7 +556,7 @@ def test_op_strict_different_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -575,7 +575,7 @@ def test_op_strict_different_measure_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["measure"] = lexpected["measure"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -594,9 +594,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_equal = qualname(self.cls.equal) - self.assertIn(qualname_equal, LENIENT) - self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue(LENIENT[self.cls.equal]) + self.assertIn(qualname_equal, _LENIENT) + self.assertTrue(_LENIENT[qualname_equal]) + self.assertTrue(_LENIENT[self.cls.equal]) def test_lenient_default(self): other = sentinel.other diff --git a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py index c44a74bc65..d3f3cbe7b4 100644 --- a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import LENIENT, qualname +from iris.common.lenient import _LENIENT, qualname from iris.common.metadata import BaseMetadata, CoordMetadata @@ -94,9 +94,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname___eq__ = qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, LENIENT) - self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[self.cls.__eq__]) + self.assertIn(qualname___eq__, _LENIENT) + self.assertTrue(_LENIENT[qualname___eq__]) + self.assertTrue(_LENIENT[self.cls.__eq__]) def test_call(self): other = sentinel.other @@ -117,7 +117,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -127,7 +127,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -138,7 +138,9 @@ def test_op_lenient_same_members_none(self): right[member] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch( + "iris.common.metadata._LENIENT", return_value=True + ): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -148,7 +150,7 @@ def test_op_lenient_different(self): right["units"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -159,7 +161,9 @@ def test_op_lenient_different_members(self): right[member] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch( + "iris.common.metadata._LENIENT", return_value=True + ): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -167,7 +171,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -177,7 +181,7 @@ def test_op_strict_different(self): right["long_name"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -189,7 +193,7 @@ def test_op_strict_different_members(self): rmetadata = self.cls(**right) with mock.patch( - "iris.common.metadata.LENIENT", return_value=False + "iris.common.metadata._LENIENT", return_value=False ): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -200,7 +204,7 @@ def test_op_strict_different_none(self): right["long_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -212,7 +216,7 @@ def test_op_strict_different_members_none(self): rmetadata = self.cls(**right) with mock.patch( - "iris.common.metadata.LENIENT", return_value=False + "iris.common.metadata._LENIENT", return_value=False ): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -240,9 +244,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_combine = qualname(self.cls.combine) - self.assertIn(qualname_combine, LENIENT) - self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[self.cls.combine]) + self.assertIn(qualname_combine, _LENIENT) + self.assertTrue(_LENIENT[qualname_combine]) + self.assertTrue(_LENIENT[self.cls.combine]) def test_lenient_default(self): other = sentinel.other @@ -278,7 +282,7 @@ def test_op_lenient_same(self): rmetadata = self.cls(**self.values) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -289,7 +293,7 @@ def test_op_lenient_same_none(self): rmetadata = self.cls(**right) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -301,7 +305,9 @@ def test_op_lenient_same_members_none(self): rmetadata = self.cls(**right) expected = right.copy() - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch( + "iris.common.metadata._LENIENT", return_value=True + ): self.assertTrue( expected, lmetadata.combine(rmetadata)._asdict() ) @@ -317,7 +323,7 @@ def test_op_lenient_different(self): expected = self.values.copy() expected["units"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -330,7 +336,9 @@ def test_op_lenient_different_members(self): expected = self.values.copy() expected[member] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch( + "iris.common.metadata._LENIENT", return_value=True + ): self.assertEqual( expected, lmetadata.combine(rmetadata)._asdict() ) @@ -343,7 +351,7 @@ def test_op_strict_same(self): rmetadata = self.cls(**self.values) expected = self.values.copy() - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -355,7 +363,7 @@ def test_op_strict_different(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -369,7 +377,7 @@ def test_op_strict_different_members(self): expected[member] = None with mock.patch( - "iris.common.metadata.LENIENT", return_value=False + "iris.common.metadata._LENIENT", return_value=False ): self.assertEqual( expected, lmetadata.combine(rmetadata)._asdict() @@ -386,7 +394,7 @@ def test_op_strict_different_none(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -400,7 +408,7 @@ def test_op_strict_different_members_none(self): expected[member] = None with mock.patch( - "iris.common.metadata.LENIENT", return_value=False + "iris.common.metadata._LENIENT", return_value=False ): self.assertEqual( expected, lmetadata.combine(rmetadata)._asdict() @@ -432,9 +440,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_difference = qualname(self.cls.difference) - self.assertIn(qualname_difference, LENIENT) - self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[self.cls.difference]) + self.assertIn(qualname_difference, _LENIENT) + self.assertTrue(_LENIENT[qualname_difference]) + self.assertTrue(_LENIENT[self.cls.difference]) def test_lenient_default(self): other = sentinel.other @@ -469,7 +477,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -479,7 +487,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -495,7 +503,9 @@ def test_op_lenient_same_members_none(self): rexpected = deepcopy(self.none)._asdict() rexpected[member] = (None, member_value) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch( + "iris.common.metadata._LENIENT", return_value=True + ): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -514,7 +524,7 @@ def test_op_lenient_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -534,7 +544,9 @@ def test_op_lenient_different_members(self): rexpected = deepcopy(self.none)._asdict() rexpected[member] = lexpected[member][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch( + "iris.common.metadata._LENIENT", return_value=True + ): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -546,7 +558,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -561,7 +573,7 @@ def test_op_strict_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -582,7 +594,7 @@ def test_op_strict_different_members(self): rexpected[member] = lexpected[member][::-1] with mock.patch( - "iris.common.metadata.LENIENT", return_value=False + "iris.common.metadata._LENIENT", return_value=False ): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() @@ -602,7 +614,7 @@ def test_op_strict_different_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -623,7 +635,7 @@ def test_op_strict_different_members_none(self): rexpected[member] = lexpected[member][::-1] with mock.patch( - "iris.common.metadata.LENIENT", return_value=False + "iris.common.metadata._LENIENT", return_value=False ): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() @@ -643,9 +655,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_equal = qualname(self.cls.equal) - self.assertIn(qualname_equal, LENIENT) - self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue(LENIENT[self.cls.equal]) + self.assertIn(qualname_equal, _LENIENT) + self.assertTrue(_LENIENT[qualname_equal]) + self.assertTrue(_LENIENT[self.cls.equal]) def test_lenient_default(self): other = sentinel.other diff --git a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py index 46f8f6f3fa..1ac0ddeb60 100644 --- a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import LENIENT, qualname +from iris.common.lenient import _LENIENT, qualname from iris.common.metadata import BaseMetadata, CubeMetadata @@ -112,9 +112,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname___eq__ = qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, LENIENT) - self.assertTrue(LENIENT[qualname___eq__]) - self.assertTrue(LENIENT[self.cls.__eq__]) + self.assertIn(qualname___eq__, _LENIENT) + self.assertTrue(_LENIENT[qualname___eq__]) + self.assertTrue(_LENIENT[self.cls.__eq__]) def test_call(self): other = sentinel.other @@ -135,7 +135,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -145,7 +145,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -155,7 +155,7 @@ def test_op_lenient_same_cell_methods_none(self): right["cell_methods"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -165,7 +165,7 @@ def test_op_lenient_different(self): right["units"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -175,7 +175,7 @@ def test_op_lenient_different_cell_methods(self): right["cell_methods"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -183,7 +183,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertTrue(lmetadata.__eq__(rmetadata)) self.assertTrue(rmetadata.__eq__(lmetadata)) @@ -193,7 +193,7 @@ def test_op_strict_different(self): right["long_name"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -203,7 +203,7 @@ def test_op_strict_different_cell_methods(self): right["cell_methods"] = self.dummy rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -213,7 +213,7 @@ def test_op_strict_different_none(self): right["long_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -223,7 +223,7 @@ def test_op_strict_different_measure_none(self): right["cell_methods"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertFalse(lmetadata.__eq__(rmetadata)) self.assertFalse(rmetadata.__eq__(lmetadata)) @@ -249,9 +249,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_combine = qualname(self.cls.combine) - self.assertIn(qualname_combine, LENIENT) - self.assertTrue(LENIENT[qualname_combine]) - self.assertTrue(LENIENT[self.cls.combine]) + self.assertIn(qualname_combine, _LENIENT) + self.assertTrue(_LENIENT[qualname_combine]) + self.assertTrue(_LENIENT[self.cls.combine]) def test_lenient_default(self): other = sentinel.other @@ -287,7 +287,7 @@ def test_op_lenient_same(self): rmetadata = self.cls(**self.values) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -298,7 +298,7 @@ def test_op_lenient_same_none(self): rmetadata = self.cls(**right) expected = self.values - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -309,7 +309,7 @@ def test_op_lenient_same_cell_methods_none(self): rmetadata = self.cls(**right) expected = right.copy() - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertTrue(expected, lmetadata.combine(rmetadata)._asdict()) self.assertTrue(expected, rmetadata.combine(lmetadata)._asdict()) @@ -321,7 +321,7 @@ def test_op_lenient_different(self): expected = self.values.copy() expected["units"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -333,7 +333,7 @@ def test_op_lenient_different_cell_methods(self): expected = self.values.copy() expected["cell_methods"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -342,7 +342,7 @@ def test_op_strict_same(self): rmetadata = self.cls(**self.values) expected = self.values.copy() - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -354,7 +354,7 @@ def test_op_strict_different(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -366,7 +366,7 @@ def test_op_strict_different_cell_methods(self): expected = self.values.copy() expected["cell_methods"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -378,7 +378,7 @@ def test_op_strict_different_none(self): expected = self.values.copy() expected["long_name"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -390,7 +390,7 @@ def test_op_strict_different_cell_methods_none(self): expected = self.values.copy() expected["cell_methods"] = None - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) @@ -416,9 +416,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_difference = qualname(self.cls.difference) - self.assertIn(qualname_difference, LENIENT) - self.assertTrue(LENIENT[qualname_difference]) - self.assertTrue(LENIENT[self.cls.difference]) + self.assertIn(qualname_difference, _LENIENT) + self.assertTrue(_LENIENT[qualname_difference]) + self.assertTrue(_LENIENT[self.cls.difference]) def test_lenient_default(self): other = sentinel.other @@ -453,7 +453,7 @@ def test_op_lenient_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -463,7 +463,7 @@ def test_op_lenient_same_none(self): right["var_name"] = None rmetadata = self.cls(**right) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -477,7 +477,7 @@ def test_op_lenient_same_cell_methods_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["cell_methods"] = (None, sentinel.cell_methods) - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -496,7 +496,7 @@ def test_op_lenient_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["units"] = lexpected["units"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -518,7 +518,7 @@ def test_op_lenient_different_cell_methods(self): rexpected = deepcopy(self.none)._asdict() rexpected["cell_methods"] = lexpected["cell_methods"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=True): + with mock.patch("iris.common.metadata._LENIENT", return_value=True): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -530,7 +530,7 @@ def test_op_strict_same(self): lmetadata = self.cls(**self.values) rmetadata = self.cls(**self.values) - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertIsNone(lmetadata.difference(rmetadata)) self.assertIsNone(rmetadata.difference(lmetadata)) @@ -545,7 +545,7 @@ def test_op_strict_different(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -567,7 +567,7 @@ def test_op_strict_different_cell_methods(self): rexpected = deepcopy(self.none)._asdict() rexpected["cell_methods"] = lexpected["cell_methods"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -586,7 +586,7 @@ def test_op_strict_different_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["long_name"] = lexpected["long_name"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -608,7 +608,7 @@ def test_op_strict_different_measure_none(self): rexpected = deepcopy(self.none)._asdict() rexpected["cell_methods"] = lexpected["cell_methods"][::-1] - with mock.patch("iris.common.metadata.LENIENT", return_value=False): + with mock.patch("iris.common.metadata._LENIENT", return_value=False): self.assertEqual( lexpected, lmetadata.difference(rmetadata)._asdict() ) @@ -627,9 +627,9 @@ def test_wraps_docstring(self): def test_lenient_service(self): qualname_equal = qualname(self.cls.equal) - self.assertIn(qualname_equal, LENIENT) - self.assertTrue(LENIENT[qualname_equal]) - self.assertTrue(LENIENT[self.cls.equal]) + self.assertIn(qualname_equal, _LENIENT) + self.assertTrue(_LENIENT[qualname_equal]) + self.assertTrue(_LENIENT[self.cls.equal]) def test_lenient_default(self): other = sentinel.other From e8b2aea3dad6a1911cee099af17c430324f5359f Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 2 Jul 2020 11:55:53 +0100 Subject: [PATCH 40/45] privitise lenient framework and add API veneer --- lib/iris/common/lenient.py | 160 ++++++++++++++---- lib/iris/common/metadata.py | 4 +- .../tests/unit/common/lenient/test_Lenient.py | 116 +++++++++++++ .../unit/common/lenient/test__Lenient.py | 118 +++++++------ ...ient_client.py => test__lenient_client.py} | 34 ++-- ...nt_service.py => test__lenient_service.py} | 24 +-- .../{test_qualname.py => test__qualname.py} | 14 +- .../test_AncillaryVariableMetadata.py | 10 +- .../unit/common/metadata/test_BaseMetadata.py | 24 +-- .../metadata/test_CellMeasureMetadata.py | 10 +- .../common/metadata/test_CoordMetadata.py | 10 +- .../unit/common/metadata/test_CubeMetadata.py | 10 +- 12 files changed, 380 insertions(+), 154 deletions(-) create mode 100644 lib/iris/tests/unit/common/lenient/test_Lenient.py rename lib/iris/tests/unit/common/lenient/{test_lenient_client.py => test__lenient_client.py} (89%) rename lib/iris/tests/unit/common/lenient/{test_lenient_service.py => test__lenient_service.py} (88%) rename lib/iris/tests/unit/common/lenient/{test_qualname.py => test__qualname.py} (82%) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 30f0bd7649..b9c595afe6 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -13,20 +13,19 @@ __all__ = [ - "lenient_client", - "lenient_service", - "qualname", + "LENIENT", + "Lenient", ] #: Default Lenient services global activation state. -LENIENT_ENABLE_DEFAULT = True +_LENIENT_ENABLE_DEFAULT = True #: Protected Lenient internal non-client, non-service keys. -LENIENT_PROTECTED = ("active", "enable") +_LENIENT_PROTECTED = ("active", "enable") -def lenient_client(*dargs, services=None): +def _lenient_client(*dargs, services=None): """ Decorator that allows a client function/method to declare at runtime that it is executing and requires lenient behaviour from a prior registered @@ -34,20 +33,20 @@ def lenient_client(*dargs, services=None): This decorator supports being called with no arguments e.g., - @lenient_client() + @_lenient_client() def func(): pass This is equivalent to using it as a simple naked decorator e.g., - @lenient_client + @_lenient_client def func() pass Alternatively, this decorator supports the lenient client explicitly declaring the lenient services that it wishes to use e.g., - @lenient_client(services=(service1, service2, ...) + @_lenient_client(services=(service1, service2, ...) def func(): pass @@ -94,7 +93,7 @@ def lenient_client_inner_naked(*args, **kwargs): as active at runtime before executing it. """ - with _LENIENT.context(active=qualname(func)): + with _LENIENT.context(active=_qualname(func)): result = func(*args, **kwargs) return result @@ -115,7 +114,7 @@ def lenient_client_inner(*args, **kwargs): as active at runtime before executing it. """ - with _LENIENT.context(*services, active=qualname(func)): + with _LENIENT.context(*services, active=_qualname(func)): result = func(*args, **kwargs) return result @@ -126,7 +125,7 @@ def lenient_client_inner(*args, **kwargs): return result -def lenient_service(*dargs): +def _lenient_service(*dargs): """ Decorator that allows a function/method to declare that it supports lenient behaviour as a service. @@ -135,13 +134,13 @@ def lenient_service(*dargs): The decorator supports being called with no arguments e.g., - @lenient_service() + @_lenient_service() def func(): pass This is equivalent to using it as a simple naked decorator e.g., - @lenient_service + @_lenient_service def func(): pass @@ -193,7 +192,7 @@ def lenient_service_outer(func): return result -def qualname(func): +def _qualname(func): """ Return the fully qualified function/method string name. @@ -216,6 +215,99 @@ def qualname(func): return result +class Lenient(threading.local): + def __init__(self, **kwargs): + """ + A container for managing the run-time lenient features and options. + + Kwargs: + + * kwargs (dict) + Mapping of lenient key/value options to enable/disable. Note that, + only the lenient "maths" options is available, which controls + lenient/strict cube arithmetic. + + For example:: + + Lenient(maths=False) + + Note that, the values of these options are thread-specific. + + """ + # This is the only public supported lenient feature i.e., cube arithmetic + self.__dict__["maths"] = None + + if not kwargs: + # If not specified, set the default behaviour of the maths lenient feature. + kwargs = dict(maths=True) + + # Configure the provided (or default) lenient features. + for feature, state in kwargs.items(): + self[feature] = state + + def __contains__(self, key): + return key in self.__dict__ + + def __getitem__(self, key): + if key not in self.__dict__: + cls = self.__class__.__name__ + emsg = f"Invalid {cls!r} option, got {key!r}." + raise KeyError(emsg) + return self.__dict__[key] + + def __repr__(self): + cls = self.__class__.__name__ + msg = f"{cls}(maths={self.__dict__['maths']!r})" + return msg + + def __setitem__(self, key, value): + cls = self.__class__.__name__ + + if key not in self.__dict__: + emsg = f"Invalid {cls!r} option, got {key!r}." + raise KeyError(emsg) + + if not isinstance(value, bool): + emsg = f"Invalid {cls!r} option {key!r} value, got {value!r}." + raise ValueError(emsg) + + self.__dict__[key] = value + # Toggle the (private) lenient behaviour. + _LENIENT.enable = value + + @contextmanager + def context(self, **kwargs): + """ + Return a context manager which allows temporary modification of the + lenient option state within the scope of the context manager. + + On entry to the context manager, all provided keyword arguments are + applied. On exit from the context manager, the previous lenient + option state is restored. + + For example:: + with iris.common.Lenient.context(maths=False): + pass + + """ + # Save the original state. + original_state = deepcopy(self.__dict__) + + # Configure the provided lenient features. + for feature, value in kwargs.items(): + self[feature] = value + + try: + yield + finally: + # Restore the original state. + self.__dict__.clear() + self.__dict__.update(original_state) + + +############################################################################### + + class _Lenient(threading.local): def __init__(self, *args, **kwargs): """ @@ -245,7 +337,7 @@ def __init__(self, *args, **kwargs): # The executing lenient client at runtime. self.__dict__["active"] = None # The global lenient services state activation switch. - self.__dict__["enable"] = LENIENT_ENABLE_DEFAULT + self.__dict__["enable"] = _LENIENT_ENABLE_DEFAULT for service in args: self.register_service(service) @@ -269,7 +361,7 @@ def __call__(self, func): """ result = False if self.__dict__["enable"]: - service = qualname(func) + service = _qualname(func) if service in self and self.__dict__[service]: active = self.__dict__["active"] if active is not None and active in self: @@ -282,6 +374,7 @@ def __call__(self, func): return result def __contains__(self, name): + name = _qualname(name) return name in self.__dict__ def __getattr__(self, name): @@ -292,7 +385,7 @@ def __getattr__(self, name): return self.__dict__[name] def __getitem__(self, name): - name = qualname(name) + name = _qualname(name) if name not in self.__dict__: cls = self.__class__.__name__ emsg = f"Invalid {cls!r} option, got {name!r}." @@ -310,7 +403,7 @@ def __repr__(self): return "{}({})".format(cls, joiner.join(kwargs)) def __setitem__(self, name, value): - name = qualname(name) + name = _qualname(name) cls = self.__class__.__name__ if name not in self.__dict__: @@ -318,7 +411,7 @@ def __setitem__(self, name, value): raise KeyError(emsg) if name == "active": - value = qualname(value) + value = _qualname(value) if not isinstance(value, str) and value is not None: emsg = f"Invalid {cls!r} option {name!r}, got {value!r}." raise ValueError(emsg) @@ -329,7 +422,7 @@ def __setitem__(self, name, value): if isinstance(value, str) or callable(value): value = (value,) if isinstance(value, Iterable): - value = tuple([qualname(item) for item in value]) + value = tuple([_qualname(item) for item in value]) self.__dict__[name] = value @contextmanager @@ -372,7 +465,7 @@ def update_client(client, services): if args: # Update the client with the provided services. - new_services = tuple([qualname(arg) for arg in args]) + new_services = tuple([_qualname(arg) for arg in args]) if active is None: # Ensure not to use "context" as the ephemeral name @@ -449,10 +542,10 @@ def register_client(self, func, services, append=False): services for the provided lenient client. Default is False. """ - func = qualname(func) + func = _qualname(func) cls = self.__class__.__name__ - if func in LENIENT_PROTECTED: + if func in _LENIENT_PROTECTED: emsg = ( f"Cannot register {cls!r} protected non-client, got {func!r}." ) @@ -462,7 +555,7 @@ def register_client(self, func, services, append=False): if not len(services): emsg = f"Require at least one {cls!r} lenient client service." raise ValueError(emsg) - services = tuple([qualname(service) for service in services]) + services = tuple([_qualname(service) for service in services]) if append: # Service order is not significant, therefore there is no # requirement to preserve it. @@ -482,8 +575,8 @@ def register_service(self, func): service function/method. """ - func = qualname(func) - if func in LENIENT_PROTECTED: + func = _qualname(func) + if func in _LENIENT_PROTECTED: cls = self.__class__.__name__ emsg = ( f"Cannot register {cls!r} protected non-service, got {func!r}." @@ -501,10 +594,10 @@ def unregister_client(self, func): A function/method of fully qualified string name of the function/method. """ - func = qualname(func) + func = _qualname(func) cls = self.__class__.__name__ - if func in LENIENT_PROTECTED: + if func in _LENIENT_PROTECTED: emsg = f"Cannot unregister {cls!r} protected non-client, got {func!r}." raise ValueError(emsg) @@ -528,10 +621,10 @@ def unregister_service(self, func): A function/method or fully qualified string name of the function/method. """ - func = qualname(func) + func = _qualname(func) cls = self.__class__.__name__ - if func in LENIENT_PROTECTED: + if func in _LENIENT_PROTECTED: emsg = f"Cannot unregister {cls!r} protected non-service, got {func!r}." raise ValueError(emsg) @@ -546,5 +639,8 @@ def unregister_service(self, func): raise ValueError(emsg) -#: Instance that manages all Iris run-time lenient client and service options. +#: (Private) Instance that manages all Iris run-time lenient client and service options. _LENIENT = _Lenient() + +#: (Public) Instance that manages all Iris run-time lenient features. +LENIENT = Lenient() diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index e81af2a95d..72e0d257a0 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -11,7 +11,9 @@ import logging import re -from .lenient import _LENIENT, lenient_service, qualname +from .lenient import _LENIENT +from .lenient import _lenient_service as lenient_service +from .lenient import _qualname as qualname __all__ = [ diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py new file mode 100644 index 0000000000..fb0076180e --- /dev/null +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -0,0 +1,116 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Unit tests for the :class:`iris.common.lenient.Lenient`. + +""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests + +from unittest.mock import sentinel + +from iris.common.lenient import Lenient, _LENIENT + + +class Test___init__(tests.IrisTest): + def test_default(self): + lenient = Lenient() + expected = dict(maths=True) + self.assertEqual(expected, lenient.__dict__) + + def test_kwargs(self): + lenient = Lenient(maths=False) + expected = dict(maths=False) + self.assertEqual(expected, lenient.__dict__) + + def test_kwargs_invalid(self): + emsg = "Invalid .* option, got 'merge'." + with self.assertRaisesRegex(KeyError, emsg): + _ = Lenient(merge=True) + + +class Test___contains__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_in(self): + self.assertTrue("maths", self.lenient) + + def test_not_in(self): + self.assertTrue(("concatenate", self.lenient)) + + +class Test___getitem__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_in(self): + self.assertTrue(self.lenient["maths"]) + + def test_not_in(self): + emsg = "Invalid .* option, got 'MATHS'." + with self.assertRaisesRegex(KeyError, emsg): + _ = self.lenient["MATHS"] + + +class Test___repr__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test(self): + expected = "Lenient(maths=True)" + self.assertEqual(expected, repr(self.lenient)) + + +class Test___setitem__(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_key_invalid(self): + emsg = "Invalid .* option, got 'MATHS." + with self.assertRaisesRegex(KeyError, emsg): + self.lenient["MATHS"] = False + + def test_maths_value_invalid(self): + value = sentinel.value + emsg = f"Invalid .* option 'maths' value, got {value!r}." + with self.assertRaisesRegex(ValueError, emsg): + self.lenient["maths"] = value + + def test_maths_disable(self): + self.assertTrue(_LENIENT.enable) + self.lenient["maths"] = False + self.assertFalse(self.lenient.__dict__["maths"]) + self.assertFalse(_LENIENT.enable) + + def test_maths_enable(self): + self.assertTrue(_LENIENT.enable) + self.lenient["maths"] = True + self.assertTrue(self.lenient.__dict__["maths"]) + self.assertTrue(_LENIENT.enable) + + +class Test_context(tests.IrisTest): + def setUp(self): + self.lenient = Lenient() + + def test_nop(self): + self.assertTrue(self.lenient["maths"]) + + with self.lenient.context(): + self.assertTrue(self.lenient["maths"]) + + self.assertTrue(self.lenient["maths"]) + + def test_maths_disable(self): + self.assertTrue(self.lenient["maths"]) + + with self.lenient.context(maths=False): + self.assertFalse(self.lenient["maths"]) + + self.assertTrue(self.lenient["maths"]) diff --git a/lib/iris/tests/unit/common/lenient/test__Lenient.py b/lib/iris/tests/unit/common/lenient/test__Lenient.py index 57f54e20ac..4555124723 100644 --- a/lib/iris/tests/unit/common/lenient/test__Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test__Lenient.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Unit tests for the :class:`iris.common.lenient.Lenient`. +Unit tests for the :class:`iris.common.lenient._Lenient`. """ @@ -15,16 +15,16 @@ from collections import Iterable from iris.common.lenient import ( - LENIENT_ENABLE_DEFAULT, - LENIENT_PROTECTED, + _LENIENT_ENABLE_DEFAULT, + _LENIENT_PROTECTED, _Lenient, - qualname, + _qualname, ) class Test___init__(tests.IrisTest): def setUp(self): - self.expected = dict(active=None, enable=LENIENT_ENABLE_DEFAULT) + self.expected = dict(active=None, enable=_LENIENT_ENABLE_DEFAULT) def test_default(self): lenient = _Lenient() @@ -52,7 +52,7 @@ def service2(): services = (service1, service2) lenient = _Lenient(*services) self.expected.update( - {qualname(service1): True, qualname(service2): True,} + {_qualname(service1): True, _qualname(service2): True,} ) self.assertEqual(self.expected, lenient.__dict__) @@ -83,8 +83,8 @@ def service1(): def service2(): pass - qualname_client1 = qualname(client1) - qualname_client2 = qualname(client2) + qualname_client1 = _qualname(client1) + qualname_client2 = _qualname(client2) clients = { qualname_client1: service1, qualname_client2: (service1, service2), @@ -92,8 +92,8 @@ def service2(): lenient = _Lenient(**clients) self.expected.update( { - qualname(client1): (qualname(service1),), - qualname(client2): (qualname(service1), qualname(service2)), + _qualname(client1): (_qualname(service1),), + _qualname(client2): (_qualname(service1), _qualname(service2)), } ) self.assertEqual(self.expected, lenient.__dict__) @@ -122,7 +122,7 @@ def test_disable_service_callable(self): def myservice(): pass - qualname_service = qualname(myservice) + qualname_service = _qualname(myservice) self.lenient.__dict__[qualname_service] = False self.assertFalse(self.lenient(myservice)) @@ -135,7 +135,7 @@ def test_service_callable_with_no_active_client(self): def myservice(): pass - qualname_service = qualname(myservice) + qualname_service = _qualname(myservice) self.lenient.__dict__[qualname_service] = True self.assertFalse(self.lenient(myservice)) @@ -154,9 +154,9 @@ def myservice(): def myclient(): pass - qualname_service = qualname(myservice) + qualname_service = _qualname(myservice) self.lenient.__dict__[qualname_service] = True - self.lenient.__dict__["active"] = qualname(myclient) + self.lenient.__dict__["active"] = _qualname(myclient) self.assertFalse(self.lenient(myservice)) def test_service_str_with_active_client_with_unmatched_registered_services( @@ -177,8 +177,8 @@ def myservice(): def myclient(): pass - qualname_service = qualname(myservice) - qualname_client = qualname(myclient) + qualname_service = _qualname(myservice) + qualname_client = _qualname(myclient) self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = qualname_client self.lenient.__dict__[qualname_client] = ("service1", "service2") @@ -200,8 +200,8 @@ def myservice(): def myclient(): pass - qualname_service = qualname(myservice) - qualname_client = qualname(myclient) + qualname_service = _qualname(myservice) + qualname_client = _qualname(myclient) self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = qualname_client self.lenient.__dict__[qualname_client] = ( @@ -229,8 +229,8 @@ def myservice(): def myclient(): pass - qualname_service = qualname(myservice) - qualname_client = qualname(myclient) + qualname_service = _qualname(myservice) + qualname_client = _qualname(myclient) self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = qualname_client self.lenient.__dict__[qualname_client] = f"{qualname_service}XXX" @@ -252,8 +252,8 @@ def myservice(): def myclient(): pass - qualname_service = qualname(myservice) - qualname_client = qualname(myclient) + qualname_service = _qualname(myservice) + qualname_client = _qualname(myclient) self.lenient.__dict__[qualname_service] = True self.lenient.__dict__["active"] = qualname_client self.lenient.__dict__[qualname_client] = qualname_service @@ -279,6 +279,16 @@ def test_in(self): def test_not_in(self): self.assertNotIn("ACTIVATE", self.lenient) + def test_in_qualname(self): + def func(): + pass + + qualname_func = _qualname(func) + lenient = _Lenient() + lenient.__dict__[qualname_func] = None + self.assertIn(func, lenient) + self.assertIn(qualname_func, lenient) + class Test___getattr__(tests.IrisTest): def setUp(self): @@ -304,7 +314,7 @@ def test_in_callable(self): def service(): pass - qualname_service = qualname(service) + qualname_service = _qualname(service) self.lenient.__dict__[qualname_service] = True self.assertTrue(self.lenient[service]) @@ -317,7 +327,7 @@ def test_not_in_callable(self): def service(): pass - qualname_service = qualname(service) + qualname_service = _qualname(service) emsg = f"Invalid .* option, got '{qualname_service}'." with self.assertRaisesRegex(KeyError, emsg): _ = self.lenient[service] @@ -344,7 +354,7 @@ def client(): pass service = "service" - qualname_client = qualname(client) + qualname_client = _qualname(client) self.lenient.__dict__[qualname_client] = None self.lenient[client] = service self.assertEqual(self.lenient.__dict__[qualname_client], (service,)) @@ -354,7 +364,7 @@ def service(): pass client = "client" - qualname_service = qualname(service) + qualname_service = _qualname(service) self.lenient.__dict__[client] = None self.lenient[client] = service self.assertEqual(self.lenient.__dict__[client], (qualname_service,)) @@ -366,8 +376,8 @@ def client(): def service(): pass - qualname_client = qualname(client) - qualname_service = qualname(service) + qualname_client = _qualname(client) + qualname_service = _qualname(service) self.lenient.__dict__[qualname_client] = None self.lenient[client] = service self.assertEqual( @@ -385,7 +395,7 @@ def test_callable_in_value_bool(self): def client(): pass - qualname_client = qualname(client) + qualname_client = _qualname(client) self.lenient.__dict__[qualname_client] = None self.lenient[client] = True self.assertTrue(self.lenient.__dict__[qualname_client]) @@ -404,7 +414,7 @@ def test_callable_in_value_iterable(self): def client(): pass - qualname_client = qualname(client) + qualname_client = _qualname(client) services = ("service1", "service2") self.lenient.__dict__[qualname_client] = None self.lenient[client] = services @@ -419,7 +429,7 @@ def service2(): client = "client" self.lenient.__dict__[client] = None - qualname_services = (qualname(service1), qualname(service2)) + qualname_services = (_qualname(service1), _qualname(service2)) self.lenient[client] = (service1, service2) self.assertEqual(self.lenient.__dict__[client], qualname_services) @@ -433,9 +443,9 @@ def service1(): def service2(): pass - qualname_client = qualname(client) + qualname_client = _qualname(client) self.lenient.__dict__[qualname_client] = None - qualname_services = (qualname(service1), qualname(service2)) + qualname_services = (_qualname(service1), _qualname(service2)) self.lenient[client] = (service1, service2) self.assertEqual( self.lenient.__dict__[qualname_client], qualname_services @@ -460,14 +470,16 @@ def client(): pass active = "active" - qualname_client = qualname(client) + qualname_client = _qualname(client) self.assertIsNone(self.lenient.__dict__[active]) self.lenient[active] = client self.assertEqual(self.lenient.__dict__[active], qualname_client) def test_enable(self): enable = "enable" - self.assertEqual(self.lenient.__dict__[enable], LENIENT_ENABLE_DEFAULT) + self.assertEqual( + self.lenient.__dict__[enable], _LENIENT_ENABLE_DEFAULT + ) self.lenient[enable] = True self.assertTrue(self.lenient.__dict__[enable]) self.lenient[enable] = False @@ -482,7 +494,7 @@ def test_enable_invalid(self): class Test_context(tests.IrisTest): def setUp(self): self.lenient = _Lenient() - self.default = dict(active=None, enable=LENIENT_ENABLE_DEFAULT) + self.default = dict(active=None, enable=_LENIENT_ENABLE_DEFAULT) def copy(self): return self.lenient.__dict__.copy() @@ -516,7 +528,7 @@ def client(): with self.lenient.context(active=client): context = self.copy() post = self.copy() - qualname_client = qualname(client) + qualname_client = _qualname(client) self.assertEqual(pre, self.default) expected = self.default.copy() expected.update(dict(active=qualname_client)) @@ -565,7 +577,7 @@ def service2(): with self.lenient.context(*services, active=client): context = self.copy() post = self.copy() - qualname_services = tuple([qualname(service) for service in services]) + qualname_services = tuple([_qualname(service) for service in services]) self.assertEqual(pre, self.default) expected = self.default.copy() expected.update(dict(active=client, client=qualname_services)) @@ -591,7 +603,7 @@ def setUp(self): self.lenient = _Lenient() def test_getter(self): - self.assertEqual(self.lenient.enable, LENIENT_ENABLE_DEFAULT) + self.assertEqual(self.lenient.enable, _LENIENT_ENABLE_DEFAULT) def test_setter_invalid(self): emsg = "Invalid .* option 'enable'" @@ -599,7 +611,7 @@ def test_setter_invalid(self): self.lenient.enable = 0 def test_setter(self): - self.assertEqual(self.lenient.enable, LENIENT_ENABLE_DEFAULT) + self.assertEqual(self.lenient.enable, _LENIENT_ENABLE_DEFAULT) self.lenient.enable = False self.assertFalse(self.lenient.enable) @@ -610,7 +622,7 @@ def setUp(self): def test_not_protected(self): emsg = "Cannot register .* protected non-client" - for protected in LENIENT_PROTECTED: + for protected in _LENIENT_PROTECTED: with self.assertRaisesRegex(ValueError, emsg): self.lenient.register_client(protected, "service") @@ -635,8 +647,8 @@ def client(): def service(): pass - qualname_client = qualname(client) - qualname_service = qualname(service) + qualname_client = _qualname(client) + qualname_service = _qualname(service) self.lenient.register_client(client, service) self.assertIn(qualname_client, self.lenient.__dict__) self.assertEqual( @@ -653,8 +665,8 @@ def service1(): def service2(): pass - qualname_client = qualname(client) - qualname_services = (qualname(service1), qualname(service2)) + qualname_client = _qualname(client) + qualname_services = (_qualname(service1), _qualname(service2)) self.lenient.register_client(client, (service1, service2)) self.assertIn(qualname_client, self.lenient.__dict__) self.assertEqual( @@ -704,7 +716,7 @@ def test_callable(self): def service(): pass - qualname_service = qualname(service) + qualname_service = _qualname(service) self.assertNotIn(qualname_service, self.lenient.__dict__) self.lenient.register_service(service) self.assertIn(qualname_service, self.lenient.__dict__) @@ -715,7 +727,7 @@ def service(): def test_not_protected(self): emsg = "Cannot register .* protected non-service" - for protected in LENIENT_PROTECTED: + for protected in _LENIENT_PROTECTED: self.lenient.__dict__[protected] = None with self.assertRaisesRegex(ValueError, emsg): self.lenient.register_service("active") @@ -727,7 +739,7 @@ def setUp(self): def test_not_protected(self): emsg = "Cannot unregister .* protected non-client" - for protected in LENIENT_PROTECTED: + for protected in _LENIENT_PROTECTED: self.lenient.__dict__[protected] = None with self.assertRaisesRegex(ValueError, emsg): self.lenient.unregister_client(protected) @@ -748,7 +760,7 @@ def test_not_client_callable(self): def client(): pass - qualname_client = qualname(client) + qualname_client = _qualname(client) self.lenient.__dict__[qualname_client] = True emsg = "Cannot unregister .* non-client" with self.assertRaisesRegex(ValueError, emsg): @@ -764,7 +776,7 @@ def test_callable(self): def client(): pass - qualname_client = qualname(client) + qualname_client = _qualname(client) self.lenient.__dict__[qualname_client] = (None,) self.lenient.unregister_client(client) self.assertNotIn(qualname_client, self.lenient.__dict__) @@ -776,7 +788,7 @@ def setUp(self): def test_not_protected(self): emsg = "Cannot unregister .* protected non-service" - for protected in LENIENT_PROTECTED: + for protected in _LENIENT_PROTECTED: self.lenient.__dict__[protected] = None with self.assertRaisesRegex(ValueError, emsg): self.lenient.unregister_service(protected) @@ -797,7 +809,7 @@ def test_not_service_callable(self): def service(): pass - qualname_service = qualname(service) + qualname_service = _qualname(service) self.lenient.__dict__[qualname_service] = (None,) emsg = "Cannot unregister .* non-service" with self.assertRaisesRegex(ValueError, emsg): @@ -813,7 +825,7 @@ def test_callable(self): def service(): pass - qualname_service = qualname(service) + qualname_service = _qualname(service) self.lenient.__dict__[qualname_service] = True self.lenient.unregister_service(service) self.assertNotIn(qualname_service, self.lenient.__dict__) diff --git a/lib/iris/tests/unit/common/lenient/test_lenient_client.py b/lib/iris/tests/unit/common/lenient/test__lenient_client.py similarity index 89% rename from lib/iris/tests/unit/common/lenient/test_lenient_client.py rename to lib/iris/tests/unit/common/lenient/test__lenient_client.py index ffca451966..29cf5e7f82 100644 --- a/lib/iris/tests/unit/common/lenient/test_lenient_client.py +++ b/lib/iris/tests/unit/common/lenient/test__lenient_client.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Unit tests for the :func:`iris.common.lenient.lenient_client`. +Unit tests for the :func:`iris.common.lenient._lenient_client`. """ @@ -15,7 +15,7 @@ from inspect import getmodule from unittest.mock import sentinel -from iris.common.lenient import _LENIENT, lenient_client +from iris.common.lenient import _LENIENT, _lenient_client class Test(tests.IrisTest): @@ -30,12 +30,12 @@ def setUp(self): def test_args_too_many(self): emsg = "Invalid lenient client arguments, expecting 1" with self.assertRaisesRegex(AssertionError, emsg): - lenient_client(None, None) + _lenient_client(None, None) def test_args_not_callable(self): emsg = "Invalid lenient client argument, expecting a callable" with self.assertRaisesRegex(AssertionError, emsg): - lenient_client(None) + _lenient_client(None) def test_args_and_kwargs(self): def func(): @@ -45,10 +45,10 @@ def func(): "Invalid lenient client, got both arguments and keyword arguments" ) with self.assertRaisesRegex(AssertionError, emsg): - lenient_client(func, services=func) + _lenient_client(func, services=func) def test_call_naked(self): - @lenient_client + @_lenient_client def myclient(): return _LENIENT.__dict__.copy() @@ -62,14 +62,14 @@ def test_call_naked_alternative(self): def myclient(): return _LENIENT.__dict__.copy() - result = lenient_client(myclient)() + result = _lenient_client(myclient)() self.assertIn(self.active, result) qualname_client = self.client.format("test_call_naked_alternative") self.assertEqual(result[self.active], qualname_client) self.assertNotIn(qualname_client, result) def test_call_naked_client_args_kwargs(self): - @lenient_client + @_lenient_client def myclient(*args, **kwargs): return args, kwargs @@ -78,14 +78,14 @@ def myclient(*args, **kwargs): self.assertEqual(kwargs_out, self.kwargs_in) def test_call_naked_doc(self): - @lenient_client + @_lenient_client def myclient(): """myclient doc-string""" self.assertEqual(myclient.__doc__, "myclient doc-string") def test_call_no_kwargs(self): - @lenient_client() + @_lenient_client() def myclient(): return _LENIENT.__dict__.copy() @@ -99,14 +99,14 @@ def test_call_no_kwargs_alternative(self): def myclient(): return _LENIENT.__dict__.copy() - result = (lenient_client())(myclient)() + result = (_lenient_client())(myclient)() self.assertIn(self.active, result) qualname_client = self.client.format("test_call_no_kwargs_alternative") self.assertEqual(result[self.active], qualname_client) self.assertNotIn(qualname_client, result) def test_call_kwargs_none(self): - @lenient_client(services=None) + @_lenient_client(services=None) def myclient(): return _LENIENT.__dict__.copy() @@ -119,7 +119,7 @@ def myclient(): def test_call_kwargs_single(self): service = sentinel.service - @lenient_client(services=service) + @_lenient_client(services=service) def myclient(): return _LENIENT.__dict__.copy() @@ -134,7 +134,7 @@ def test_call_kwargs_single_callable(self): def myservice(): pass - @lenient_client(services=myservice) + @_lenient_client(services=myservice) def myclient(): return _LENIENT.__dict__.copy() @@ -150,7 +150,7 @@ def myclient(): def test_call_kwargs_iterable(self): services = (sentinel.service1, sentinel.service2) - @lenient_client(services=services) + @_lenient_client(services=services) def myclient(): return _LENIENT.__dict__.copy() @@ -162,7 +162,7 @@ def myclient(): self.assertEqual(set(result[qualname_client]), set(services)) def test_call_client_args_kwargs(self): - @lenient_client() + @_lenient_client() def myclient(*args, **kwargs): return args, kwargs @@ -171,7 +171,7 @@ def myclient(*args, **kwargs): self.assertEqual(kwargs_out, self.kwargs_in) def test_call_doc(self): - @lenient_client() + @_lenient_client() def myclient(): """myclient doc-string""" diff --git a/lib/iris/tests/unit/common/lenient/test_lenient_service.py b/lib/iris/tests/unit/common/lenient/test__lenient_service.py similarity index 88% rename from lib/iris/tests/unit/common/lenient/test_lenient_service.py rename to lib/iris/tests/unit/common/lenient/test__lenient_service.py index 9def7dc9d6..3b019c9de5 100644 --- a/lib/iris/tests/unit/common/lenient/test_lenient_service.py +++ b/lib/iris/tests/unit/common/lenient/test__lenient_service.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Unit tests for the :func:`iris.common.lenient.lenient_service`. +Unit tests for the :func:`iris.common.lenient._lenient_service`. """ @@ -15,7 +15,7 @@ from inspect import getmodule from unittest.mock import sentinel -from iris.common.lenient import _LENIENT, lenient_service +from iris.common.lenient import _LENIENT, _lenient_service class Test(tests.IrisTest): @@ -28,15 +28,15 @@ def setUp(self): def test_args_too_many(self): emsg = "Invalid lenient service arguments, expecting 1" with self.assertRaisesRegex(AssertionError, emsg): - lenient_service(None, None) + _lenient_service(None, None) def test_args_not_callable(self): emsg = "Invalid lenient service argument, expecting a callable" with self.assertRaisesRegex(AssertionError, emsg): - lenient_service(None) + _lenient_service(None) def test_call_naked(self): - @lenient_service + @_lenient_service def myservice(): return _LENIENT.__dict__.copy() @@ -53,12 +53,12 @@ def myservice(): return _LENIENT.__dict__.copy() qualname_service = self.service.format("test_call_naked_alternative") - result = lenient_service(myservice)() + result = _lenient_service(myservice)() self.assertIn(qualname_service, result) self.assertTrue(result[qualname_service]) def test_call_naked_service_args_kwargs(self): - @lenient_service + @_lenient_service def myservice(*args, **kwargs): return args, kwargs @@ -67,14 +67,14 @@ def myservice(*args, **kwargs): self.assertEqual(kwargs_out, self.kwargs_in) def test_call_naked_doc(self): - @lenient_service + @_lenient_service def myservice(): """myservice doc-string""" self.assertEqual(myservice.__doc__, "myservice doc-string") def test_call(self): - @lenient_service() + @_lenient_service() def myservice(): return _LENIENT.__dict__.copy() @@ -91,12 +91,12 @@ def myservice(): return _LENIENT.__dict__.copy() qualname_service = self.service.format("test_call_alternative") - result = (lenient_service())(myservice)() + result = (_lenient_service())(myservice)() self.assertIn(qualname_service, result) self.assertTrue(result[qualname_service]) def test_call_service_args_kwargs(self): - @lenient_service() + @_lenient_service() def myservice(*args, **kwargs): return args, kwargs @@ -105,7 +105,7 @@ def myservice(*args, **kwargs): self.assertEqual(kwargs_out, self.kwargs_in) def test_call_doc(self): - @lenient_service() + @_lenient_service() def myservice(): """myservice doc-string""" diff --git a/lib/iris/tests/unit/common/lenient/test_qualname.py b/lib/iris/tests/unit/common/lenient/test__qualname.py similarity index 82% rename from lib/iris/tests/unit/common/lenient/test_qualname.py rename to lib/iris/tests/unit/common/lenient/test__qualname.py index a5622873d0..e233b2ac78 100644 --- a/lib/iris/tests/unit/common/lenient/test_qualname.py +++ b/lib/iris/tests/unit/common/lenient/test__qualname.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Unit tests for the :func:`iris.common.lenient.qualname`. +Unit tests for the :func:`iris.common.lenient._qualname`. """ @@ -15,7 +15,7 @@ from inspect import getmodule from unittest.mock import sentinel -from iris.common.lenient import qualname +from iris.common.lenient import _qualname class Test(tests.IrisTest): @@ -25,7 +25,7 @@ def setUp(self): def test_pass_thru_non_callable(self): func = sentinel.func - result = qualname(func) + result = _qualname(func) self.assertEqual(result, func) def test_callable_function_local(self): @@ -35,13 +35,13 @@ def myfunc(): qualname_func = self.locals.format( "test_callable_function_local", "myfunc" ) - result = qualname(myfunc) + result = _qualname(myfunc) self.assertEqual(result, qualname_func) def test_callable_function(self): import iris - result = qualname(iris.load) + result = _qualname(iris.load) self.assertEqual(result, "iris.load") def test_callable_method_local(self): @@ -52,13 +52,13 @@ def mymethod(self): qualname_method = self.locals.format( "test_callable_method_local", "MyClass.mymethod" ) - result = qualname(MyClass.mymethod) + result = _qualname(MyClass.mymethod) self.assertEqual(result, qualname_method) def test_callable_method(self): import iris - result = qualname(iris.cube.Cube.add_ancillary_variable) + result = _qualname(iris.cube.Cube.add_ancillary_variable) self.assertEqual(result, "iris.cube.Cube.add_ancillary_variable") diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py index 0a083038e9..53e16948fd 100644 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import _LENIENT, qualname +from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, AncillaryVariableMetadata @@ -82,7 +82,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname___eq__ = qualname(self.cls.__eq__) + qualname___eq__ = _qualname(self.cls.__eq__) self.assertIn(qualname___eq__, _LENIENT) self.assertTrue(_LENIENT[qualname___eq__]) self.assertTrue(_LENIENT[self.cls.__eq__]) @@ -178,7 +178,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_combine = qualname(self.cls.combine) + qualname_combine = _qualname(self.cls.combine) self.assertIn(qualname_combine, _LENIENT) self.assertTrue(_LENIENT[qualname_combine]) self.assertTrue(_LENIENT[self.cls.combine]) @@ -297,7 +297,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_difference = qualname(self.cls.difference) + qualname_difference = _qualname(self.cls.difference) self.assertIn(qualname_difference, _LENIENT) self.assertTrue(_LENIENT[qualname_difference]) self.assertTrue(_LENIENT[self.cls.difference]) @@ -424,7 +424,7 @@ def test_wraps_docstring(self): self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) def test_lenient_service(self): - qualname_equal = qualname(self.cls.equal) + qualname_equal = _qualname(self.cls.equal) self.assertIn(qualname_equal, _LENIENT) self.assertTrue(_LENIENT[qualname_equal]) self.assertTrue(_LENIENT[self.cls.equal]) diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index f7b62a4ba8..906f1e99b5 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import _LENIENT, qualname +from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, CubeMetadata @@ -74,7 +74,7 @@ def setUp(self): self.metadata = self.cls(**self.kwargs) def test_lenient_service(self): - qualname___eq__ = qualname(self.cls.__eq__) + qualname___eq__ = _qualname(self.cls.__eq__) self.assertIn(qualname___eq__, _LENIENT) self.assertTrue(_LENIENT[qualname___eq__]) self.assertTrue(_LENIENT[self.cls.__eq__]) @@ -106,7 +106,7 @@ def test_lenient(self): self.assertEqual(1, mlenient.call_count) (arg,), kwargs = mlenient.call_args - self.assertEqual(qualname(self.cls.__eq__), qualname(arg)) + self.assertEqual(_qualname(self.cls.__eq__), _qualname(arg)) self.assertEqual(dict(), kwargs) def test_strict_same(self): @@ -1096,7 +1096,7 @@ def setUp(self): ) def test_lenient_service(self): - qualname_combine = qualname(self.cls.combine) + qualname_combine = _qualname(self.cls.combine) self.assertIn(qualname_combine, _LENIENT) self.assertTrue(_LENIENT[qualname_combine]) self.assertTrue(_LENIENT[self.cls.combine]) @@ -1135,7 +1135,7 @@ def test_lenient_true(self): self.assertEqual(1, mcontext.call_count) (arg,), kwargs = mcontext.call_args - self.assertEqual(qualname(self.cls.combine), arg) + self.assertEqual(_qualname(self.cls.combine), arg) self.assertEqual(dict(), kwargs) self.assertEqual(result._asdict(), self.mock_kwargs) @@ -1155,7 +1155,7 @@ def test_lenient_false(self): self.assertEqual(1, mcontext.call_count) args, kwargs = mcontext.call_args self.assertEqual((), args) - self.assertEqual({qualname(self.cls.combine): False}, kwargs) + self.assertEqual({_qualname(self.cls.combine): False}, kwargs) self.assertEqual(self.mock_kwargs, result._asdict()) self.assertEqual(1, mcombine.call_count) @@ -1184,7 +1184,7 @@ def setUp(self): ) def test_lenient_service(self): - qualname_difference = qualname(self.cls.difference) + qualname_difference = _qualname(self.cls.difference) self.assertIn(qualname_difference, _LENIENT) self.assertTrue(_LENIENT[qualname_difference]) self.assertTrue(_LENIENT[self.cls.difference]) @@ -1223,7 +1223,7 @@ def test_lenient_true(self): self.assertEqual(1, mcontext.call_count) (arg,), kwargs = mcontext.call_args - self.assertEqual(qualname(self.cls.difference), arg) + self.assertEqual(_qualname(self.cls.difference), arg) self.assertEqual(dict(), kwargs) self.assertEqual(self.mock_kwargs, result._asdict()) @@ -1243,7 +1243,7 @@ def test_lenient_false(self): self.assertEqual(mcontext.call_count, 1) args, kwargs = mcontext.call_args self.assertEqual((), args) - self.assertEqual({qualname(self.cls.difference): False}, kwargs) + self.assertEqual({_qualname(self.cls.difference): False}, kwargs) self.assertEqual(self.mock_kwargs, result._asdict()) self.assertEqual(1, mdifference.call_count) @@ -1265,7 +1265,7 @@ def setUp(self): self.metadata = self.cls(**kwargs) def test_lenient_service(self): - qualname_equal = qualname(self.cls.equal) + qualname_equal = _qualname(self.cls.equal) self.assertIn(qualname_equal, _LENIENT) self.assertTrue(_LENIENT[qualname_equal]) self.assertTrue((_LENIENT[self.cls.equal])) @@ -1305,7 +1305,7 @@ def test_lenient_true(self): self.assertEqual(return_value, result) self.assertEqual(1, mcontext.call_count) (arg,), kwargs = mcontext.call_args - self.assertEqual(qualname(self.cls.equal), arg) + self.assertEqual(_qualname(self.cls.equal), arg) self.assertEqual(dict(), kwargs) self.assertEqual(1, m__eq__.call_count) @@ -1324,7 +1324,7 @@ def test_lenient_false(self): self.assertEqual(1, mcontext.call_count) args, kwargs = mcontext.call_args self.assertEqual((), args) - self.assertEqual({qualname(self.cls.equal): False}, kwargs) + self.assertEqual({_qualname(self.cls.equal): False}, kwargs) self.assertEqual(return_value, result) self.assertEqual(1, m__eq__.call_count) diff --git a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py index 5bf8158145..e5ed287fea 100644 --- a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import _LENIENT, qualname +from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, CellMeasureMetadata @@ -87,7 +87,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname___eq__ = qualname(self.cls.__eq__) + qualname___eq__ = _qualname(self.cls.__eq__) self.assertIn(qualname___eq__, _LENIENT) self.assertTrue(_LENIENT[qualname___eq__]) self.assertTrue(_LENIENT[self.cls.__eq__]) @@ -224,7 +224,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_combine = qualname(self.cls.combine) + qualname_combine = _qualname(self.cls.combine) self.assertIn(qualname_combine, _LENIENT) self.assertTrue(_LENIENT[qualname_combine]) self.assertTrue(_LENIENT[self.cls.combine]) @@ -391,7 +391,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_difference = qualname(self.cls.difference) + qualname_difference = _qualname(self.cls.difference) self.assertIn(qualname_difference, _LENIENT) self.assertTrue(_LENIENT[qualname_difference]) self.assertTrue(_LENIENT[self.cls.difference]) @@ -593,7 +593,7 @@ def test_wraps_docstring(self): self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) def test_lenient_service(self): - qualname_equal = qualname(self.cls.equal) + qualname_equal = _qualname(self.cls.equal) self.assertIn(qualname_equal, _LENIENT) self.assertTrue(_LENIENT[qualname_equal]) self.assertTrue(_LENIENT[self.cls.equal]) diff --git a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py index d3f3cbe7b4..ee79201541 100644 --- a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import _LENIENT, qualname +from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, CoordMetadata @@ -93,7 +93,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname___eq__ = qualname(self.cls.__eq__) + qualname___eq__ = _qualname(self.cls.__eq__) self.assertIn(qualname___eq__, _LENIENT) self.assertTrue(_LENIENT[qualname___eq__]) self.assertTrue(_LENIENT[self.cls.__eq__]) @@ -243,7 +243,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_combine = qualname(self.cls.combine) + qualname_combine = _qualname(self.cls.combine) self.assertIn(qualname_combine, _LENIENT) self.assertTrue(_LENIENT[qualname_combine]) self.assertTrue(_LENIENT[self.cls.combine]) @@ -439,7 +439,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_difference = qualname(self.cls.difference) + qualname_difference = _qualname(self.cls.difference) self.assertIn(qualname_difference, _LENIENT) self.assertTrue(_LENIENT[qualname_difference]) self.assertTrue(_LENIENT[self.cls.difference]) @@ -654,7 +654,7 @@ def test_wraps_docstring(self): self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) def test_lenient_service(self): - qualname_equal = qualname(self.cls.equal) + qualname_equal = _qualname(self.cls.equal) self.assertIn(qualname_equal, _LENIENT) self.assertTrue(_LENIENT[qualname_equal]) self.assertTrue(_LENIENT[self.cls.equal]) diff --git a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py index 1ac0ddeb60..b4f185d7bd 100644 --- a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py @@ -16,7 +16,7 @@ import unittest.mock as mock from unittest.mock import sentinel -from iris.common.lenient import _LENIENT, qualname +from iris.common.lenient import _LENIENT, _qualname from iris.common.metadata import BaseMetadata, CubeMetadata @@ -111,7 +111,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname___eq__ = qualname(self.cls.__eq__) + qualname___eq__ = _qualname(self.cls.__eq__) self.assertIn(qualname___eq__, _LENIENT) self.assertTrue(_LENIENT[qualname___eq__]) self.assertTrue(_LENIENT[self.cls.__eq__]) @@ -248,7 +248,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_combine = qualname(self.cls.combine) + qualname_combine = _qualname(self.cls.combine) self.assertIn(qualname_combine, _LENIENT) self.assertTrue(_LENIENT[qualname_combine]) self.assertTrue(_LENIENT[self.cls.combine]) @@ -415,7 +415,7 @@ def test_wraps_docstring(self): ) def test_lenient_service(self): - qualname_difference = qualname(self.cls.difference) + qualname_difference = _qualname(self.cls.difference) self.assertIn(qualname_difference, _LENIENT) self.assertTrue(_LENIENT[qualname_difference]) self.assertTrue(_LENIENT[self.cls.difference]) @@ -626,7 +626,7 @@ def test_wraps_docstring(self): self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) def test_lenient_service(self): - qualname_equal = qualname(self.cls.equal) + qualname_equal = _qualname(self.cls.equal) self.assertIn(qualname_equal, _LENIENT) self.assertTrue(_LENIENT[qualname_equal]) self.assertTrue(_LENIENT[self.cls.equal]) From b8511641ac3369d294c5a78972f50d3e4b00cd5a Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 2 Jul 2020 12:34:10 +0100 Subject: [PATCH 41/45] add explicit maths feature default --- lib/iris/common/lenient.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index b9c595afe6..67e751e16f 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -18,10 +18,13 @@ ] -#: Default Lenient services global activation state. +#: Default _Lenient services global activation state. _LENIENT_ENABLE_DEFAULT = True -#: Protected Lenient internal non-client, non-service keys. +#: Default Lenient maths feature state. +_LENIENT_MATHS_DEFAULT = True + +#: Protected _Lenient internal non-client, non-service keys. _LENIENT_PROTECTED = ("active", "enable") @@ -239,7 +242,7 @@ def __init__(self, **kwargs): if not kwargs: # If not specified, set the default behaviour of the maths lenient feature. - kwargs = dict(maths=True) + kwargs = dict(maths=_LENIENT_MATHS_DEFAULT) # Configure the provided (or default) lenient features. for feature, state in kwargs.items(): From 8d3a0e011a613c39081162b9942f8eadac2ef937 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 2 Jul 2020 15:13:59 +0100 Subject: [PATCH 42/45] review actions --- lib/iris/common/lenient.py | 8 ++++---- lib/iris/common/metadata.py | 9 +++++---- lib/iris/tests/unit/common/lenient/test__Lenient.py | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 67e751e16f..51df8e9f40 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -326,7 +326,7 @@ def __init__(self, *args, **kwargs): Kwargs: * kwargs (dict of callable/str or iterable of callable/str) - Mapping of lenient client function/method, or fully qualified sting name + Mapping of lenient client function/method, or fully qualified string name of the function/method, to one or more lenient service function/methods or fully qualified string name of function/methods. @@ -556,12 +556,12 @@ def register_client(self, func, services, append=False): if isinstance(services, str) or not isinstance(services, Iterable): services = (services,) if not len(services): - emsg = f"Require at least one {cls!r} lenient client service." + emsg = f"Require at least one {cls!r} client service." raise ValueError(emsg) services = tuple([_qualname(service) for service in services]) if append: - # Service order is not significant, therefore there is no - # requirement to preserve it. + # The original provided service order is not significant. There is + # no requirement to preserve it, so it's safe to sort. existing = self.__dict__[func] if func in self else () services = tuple(sorted(set(existing) | set(services))) self.__dict__[func] = services diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 72e0d257a0..3b3757d16d 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -779,10 +779,11 @@ def _compare_lenient(self, other): Boolean. """ - # Perform "strict" comparison for "coord_system" and "climatological". - result = ( - self.coord_system == other.coord_system - and self.climatological == other.climatological + result = all( + [ + getattr(self, field) == getattr(other, field) + for field in self._members + ] ) if result: # Perform lenient comparison of the other parent members. diff --git a/lib/iris/tests/unit/common/lenient/test__Lenient.py b/lib/iris/tests/unit/common/lenient/test__Lenient.py index 4555124723..82824d4fc7 100644 --- a/lib/iris/tests/unit/common/lenient/test__Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test__Lenient.py @@ -674,7 +674,7 @@ def service2(): ) def test_services_empty(self): - emsg = "Require at least one .* lenient client service." + emsg = "Require at least one .* client service." with self.assertRaisesRegex(ValueError, emsg): self.lenient.register_client("client", ()) From d8e5f160c74026fd79510a4c72af737f6ea2ef1a Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 3 Jul 2020 12:02:56 +0100 Subject: [PATCH 43/45] review actions --- .../unit/common/metadata/test_BaseMetadata.py | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py index 906f1e99b5..600a26fb16 100644 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py @@ -83,7 +83,7 @@ def test_cannot_compare_non_class(self): result = self.metadata.__eq__(None) self.assertIs(NotImplemented, result) - def test_cannot_compare(self): + def test_cannot_compare_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) result = self.metadata.__eq__(other) self.assertIs(NotImplemented, result) @@ -258,9 +258,9 @@ def test_strict_units(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) - self.assertEqual(expected, result) + self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) + self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) def test_strict_units_different(self): left = self.none.copy() @@ -326,9 +326,9 @@ def test_attributes_non_mapping_different(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.copy().values()) - self.assertEqual(expected, result) + self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) + self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) def test_attributes_non_mapping_different_none(self): left = self.none.copy() @@ -354,9 +354,9 @@ def test_names(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - result = lmetadata._combine_lenient(rmetadata) expected = list(left.values()) - self.assertEqual(expected, result) + self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) + self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) def test_names_different(self): dummy = sentinel.dummy @@ -369,9 +369,9 @@ def test_names_different(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - result = lmetadata._combine_lenient(rmetadata) expected = list(self.none.copy().values()) - self.assertEqual(expected, result) + self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) + self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) def test_names_different_none(self): left = self.none.copy() @@ -409,6 +409,9 @@ def test_same(self): expected = dict(**left) self.assertEqual(expected, result) + result = self.metadata._combine_lenient_attributes(right, left) + self.assertEqual(expected, result) + def test_different(self): left = self.values.copy() right = self.values.copy() @@ -420,6 +423,9 @@ def test_different(self): del expected[key] self.assertEqual(dict(expected), result) + result = self.metadata._combine_lenient_attributes(right, left) + self.assertEqual(dict(expected), result) + def test_different_none(self): left = self.values.copy() right = self.values.copy() @@ -431,6 +437,9 @@ def test_different_none(self): del expected[key] self.assertEqual(dict(expected), result) + result = self.metadata._combine_lenient_attributes(right, left) + self.assertEqual(dict(expected), result) + def test_extra(self): left = self.values.copy() right = self.values.copy() @@ -443,6 +452,9 @@ def test_extra(self): expected["extra_right"] = right["extra_right"] self.assertEqual(dict(expected), result) + result = self.metadata._combine_lenient_attributes(right, left) + self.assertEqual(dict(expected), result) + class Test__compare_lenient(tests.IrisTest): def setUp(self): @@ -580,7 +592,7 @@ def test_attributes_non_mapping_different_none(self): rmetadata = self.cls(**right) self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._combine_lenient(lmetadata)) + self.assertTrue(rmetadata._compare_lenient(lmetadata)) def test_names(self): left = self.none.copy() @@ -740,9 +752,9 @@ def test_strict_units(self): right = left.copy() lmetadata = self.cls(**left) rmetadata = self.cls(**right) - result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.values()) - self.assertEqual(expected, result) + self.assertEqual(expected, lmetadata._difference_lenient(rmetadata)) + self.assertEqual(expected, rmetadata._difference_lenient(lmetadata)) def test_strict_units_different(self): left = self.none.copy() @@ -857,9 +869,9 @@ def test_names(self): lmetadata = self.cls(**left) rmetadata = self.cls(**right) - result = lmetadata._difference_lenient(rmetadata) expected = list(self.none.values()) - self.assertEqual(expected, result) + self.assertEqual(expected, lmetadata._difference_lenient(rmetadata)) + self.assertEqual(expected, rmetadata._difference_lenient(lmetadata)) def test_names_different(self): dummy = sentinel.dummy @@ -925,9 +937,13 @@ def setUp(self): def test_same(self): left = self.values.copy() right = self.values.copy() + result = self.metadata._difference_lenient_attributes(left, right) self.assertIsNone(result) + result = self.metadata._difference_lenient_attributes(right, left) + self.assertIsNone(result) + def test_different(self): left = self.values.copy() right = self.values.copy() @@ -971,6 +987,9 @@ def test_extra(self): expected["extra_right"] = right["extra_right"] self.assertIsNone(result) + result = self.metadata._difference_lenient_attributes(right, left) + self.assertIsNone(result) + class Test__difference_strict_attributes(tests.IrisTest): def setUp(self): @@ -991,6 +1010,8 @@ def test_same(self): result = self.metadata._difference_strict_attributes(left, right) self.assertIsNone(result) + result = self.metadata._difference_strict_attributes(right, left) + self.assertIsNone(result) def test_different(self): left = self.values.copy() @@ -1106,7 +1127,7 @@ def test_cannot_combine_non_class(self): with self.assertRaisesRegex(TypeError, emsg): self.metadata.combine(None) - def test_cannot_combine(self): + def test_cannot_combine_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) emsg = "Cannot combine" with self.assertRaisesRegex(TypeError, emsg): @@ -1194,7 +1215,7 @@ def test_cannot_differ_non_class(self): with self.assertRaisesRegex(TypeError, emsg): self.metadata.difference(None) - def test_cannot_differ(self): + def test_cannot_differ_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) emsg = "Cannot differ" with self.assertRaisesRegex(TypeError, emsg): @@ -1275,7 +1296,7 @@ def test_cannot_compare_non_class(self): with self.assertRaisesRegex(TypeError, emsg): self.metadata.equal(None) - def test_cannot_compare(self): + def test_cannot_compare_different_class(self): other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) emsg = "Cannot compare" with self.assertRaisesRegex(TypeError, emsg): From 972cd296cfa06f668024e2a160b718ae50cb11a0 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 3 Jul 2020 14:12:38 +0100 Subject: [PATCH 44/45] trexfeathers review actions --- lib/iris/common/lenient.py | 22 ++++++++++--------- lib/iris/common/metadata.py | 2 +- lib/iris/common/mixin.py | 13 +++++------ .../unit/common/lenient/test__Lenient.py | 16 +++++++------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 51df8e9f40..9e7fbe7065 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -416,7 +416,7 @@ def __setitem__(self, name, value): if name == "active": value = _qualname(value) if not isinstance(value, str) and value is not None: - emsg = f"Invalid {cls!r} option {name!r}, got {value!r}." + emsg = f"Invalid {cls!r} option {name!r}, expected a registered {cls!r} client, got {value!r}." raise ValueError(emsg) self.__dict__[name] = value elif name == "enable": @@ -519,7 +519,7 @@ def enable(self, state): """ if not isinstance(state, bool): cls = self.__class__.__name__ - emsg = f"Invalid {cls!r} option 'enable', got {state!r}." + emsg = f"Invalid {cls!r} option 'enable', expected a {type(True)!r}, got {state!r}." raise ValueError(emsg) self.__dict__["enable"] = state @@ -550,7 +550,8 @@ def register_client(self, func, services, append=False): if func in _LENIENT_PROTECTED: emsg = ( - f"Cannot register {cls!r} protected non-client, got {func!r}." + f"Cannot register {cls!r} client. " + f"Please rename your client to be something other than {func!r}." ) raise ValueError(emsg) if isinstance(services, str) or not isinstance(services, Iterable): @@ -582,7 +583,8 @@ def register_service(self, func): if func in _LENIENT_PROTECTED: cls = self.__class__.__name__ emsg = ( - f"Cannot register {cls!r} protected non-service, got {func!r}." + f"Cannot register {cls!r} service. " + f"Please rename your service to be something other than {func!r}." ) raise ValueError(emsg) self.__dict__[func] = True @@ -601,17 +603,17 @@ def unregister_client(self, func): cls = self.__class__.__name__ if func in _LENIENT_PROTECTED: - emsg = f"Cannot unregister {cls!r} protected non-client, got {func!r}." + emsg = f"Cannot unregister {cls!r} client, as {func!r} is a protected {cls!r} option." raise ValueError(emsg) if func in self.__dict__: value = self.__dict__[func] if isinstance(value, bool): - emsg = f"Cannot unregister {cls!r} non-client, got {func!r}." + emsg = f"Cannot unregister {cls!r} client, as {func!r} is not a valid {cls!r} client." raise ValueError(emsg) del self.__dict__[func] else: - emsg = f"Cannot unregister unknown {cls!r} client, got {func!r}." + emsg = f"Cannot unregister unknown {cls!r} client {func!r}." raise ValueError(emsg) def unregister_service(self, func): @@ -628,17 +630,17 @@ def unregister_service(self, func): cls = self.__class__.__name__ if func in _LENIENT_PROTECTED: - emsg = f"Cannot unregister {cls!r} protected non-service, got {func!r}." + emsg = f"Cannot unregister {cls!r} service, as {func!r} is a protected {cls!r} option." raise ValueError(emsg) if func in self.__dict__: value = self.__dict__[func] if not isinstance(value, bool): - emsg = f"Cannot unregister {cls!r} non-service, got {func!r}." + emsg = f"Cannot unregister {cls!r} service, as {func!r} is not a valid {cls!r} service." raise ValueError(emsg) del self.__dict__[func] else: - emsg = f"Cannot unregister unknown {cls!r} service, got {func!r}." + emsg = f"Cannot unregister unknown {cls!r} service {func!r}." raise ValueError(emsg) diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 3b3757d16d..eba3ffc6e8 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -157,7 +157,7 @@ def _api_common( self, other, func_service, func_operation, action, lenient=None ): """ - Common entry-point for API facing lenient service methods. + Common entry-point for lenient metadata API methods. Args: diff --git a/lib/iris/common/mixin.py b/lib/iris/common/mixin.py index d68c556fa2..8ce3b21f1c 100644 --- a/lib/iris/common/mixin.py +++ b/lib/iris/common/mixin.py @@ -215,7 +215,6 @@ def metadata(self, metadata): metadata = metadata._asdict() if isinstance(metadata, Mapping): - missing = False fields = [field for field in fields if field in metadata] else: # Generic iterable/container with no associated keys. @@ -225,12 +224,12 @@ def metadata(self, metadata): if not hasattr(metadata, field) ] - if missing: - missing = ", ".join( - map(lambda i: "{!r}".format(i), missing) - ) - emsg = "Invalid {!r} metadata, require {} to be specified." - raise TypeError(emsg.format(type(arg), missing)) + if missing: + missing = ", ".join( + map(lambda i: "{!r}".format(i), missing) + ) + emsg = "Invalid {!r} metadata, require {} to be specified." + raise TypeError(emsg.format(type(arg), missing)) for field in fields: if hasattr(metadata, field): diff --git a/lib/iris/tests/unit/common/lenient/test__Lenient.py b/lib/iris/tests/unit/common/lenient/test__Lenient.py index 82824d4fc7..cdcf3df9a3 100644 --- a/lib/iris/tests/unit/common/lenient/test__Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test__Lenient.py @@ -621,7 +621,7 @@ def setUp(self): self.lenient = _Lenient() def test_not_protected(self): - emsg = "Cannot register .* protected non-client" + emsg = "Cannot register .* client" for protected in _LENIENT_PROTECTED: with self.assertRaisesRegex(ValueError, emsg): self.lenient.register_client(protected, "service") @@ -726,7 +726,7 @@ def service(): self.assertTrue(self.lenient.__dict__[qualname_service]) def test_not_protected(self): - emsg = "Cannot register .* protected non-service" + emsg = "Cannot register .* service" for protected in _LENIENT_PROTECTED: self.lenient.__dict__[protected] = None with self.assertRaisesRegex(ValueError, emsg): @@ -738,7 +738,7 @@ def setUp(self): self.lenient = _Lenient() def test_not_protected(self): - emsg = "Cannot unregister .* protected non-client" + emsg = "Cannot unregister .* client, as .* is a protected .* option." for protected in _LENIENT_PROTECTED: self.lenient.__dict__[protected] = None with self.assertRaisesRegex(ValueError, emsg): @@ -752,7 +752,7 @@ def test_not_in(self): def test_not_client(self): client = "client" self.lenient.__dict__[client] = True - emsg = "Cannot unregister .* non-client" + emsg = "Cannot unregister .* client, as .* is not a valid .* client." with self.assertRaisesRegex(ValueError, emsg): self.lenient.unregister_client(client) @@ -762,7 +762,7 @@ def client(): qualname_client = _qualname(client) self.lenient.__dict__[qualname_client] = True - emsg = "Cannot unregister .* non-client" + emsg = "Cannot unregister .* client, as .* is not a valid .* client." with self.assertRaisesRegex(ValueError, emsg): self.lenient.unregister_client(client) @@ -787,7 +787,7 @@ def setUp(self): self.lenient = _Lenient() def test_not_protected(self): - emsg = "Cannot unregister .* protected non-service" + emsg = "Cannot unregister .* service, as .* is a protected .* option." for protected in _LENIENT_PROTECTED: self.lenient.__dict__[protected] = None with self.assertRaisesRegex(ValueError, emsg): @@ -801,7 +801,7 @@ def test_not_in(self): def test_not_service(self): service = "service" self.lenient.__dict__[service] = (None,) - emsg = "Cannot unregister .* non-service" + emsg = "Cannot unregister .* service, as .* is not a valid .* service." with self.assertRaisesRegex(ValueError, emsg): self.lenient.unregister_service(service) @@ -811,7 +811,7 @@ def service(): qualname_service = _qualname(service) self.lenient.__dict__[qualname_service] = (None,) - emsg = "Cannot unregister .* non-service" + emsg = "Cannot unregister .* service, as .* is not a valid .* service." with self.assertRaisesRegex(ValueError, emsg): self.lenient.unregister_service(service) From de655070e6c56298b0dc09caf2ec00fb273246e6 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 3 Jul 2020 16:37:42 +0100 Subject: [PATCH 45/45] stephenworsley review actions --- lib/iris/common/lenient.py | 20 ++++-- .../tests/unit/common/lenient/test_Lenient.py | 72 ++++++++++++++++++- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/lib/iris/common/lenient.py b/lib/iris/common/lenient.py index 9e7fbe7065..802d854554 100644 --- a/lib/iris/common/lenient.py +++ b/lib/iris/common/lenient.py @@ -237,8 +237,8 @@ def __init__(self, **kwargs): Note that, the values of these options are thread-specific. """ - # This is the only public supported lenient feature i.e., cube arithmetic - self.__dict__["maths"] = None + # Configure the initial default lenient state. + self._init() if not kwargs: # If not specified, set the default behaviour of the maths lenient feature. @@ -278,6 +278,11 @@ def __setitem__(self, key, value): # Toggle the (private) lenient behaviour. _LENIENT.enable = value + def _init(self): + """Configure the initial default lenient state.""" + # This is the only public supported lenient feature i.e., cube arithmetic + self.__dict__["maths"] = None + @contextmanager def context(self, **kwargs): """ @@ -293,19 +298,24 @@ def context(self, **kwargs): pass """ + + def configure_state(state): + for feature, value in state.items(): + self[feature] = value + # Save the original state. original_state = deepcopy(self.__dict__) # Configure the provided lenient features. - for feature, value in kwargs.items(): - self[feature] = value + configure_state(kwargs) try: yield finally: # Restore the original state. self.__dict__.clear() - self.__dict__.update(original_state) + self._init() + configure_state(original_state) ############################################################################### diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py index fb0076180e..8ca98342ca 100644 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ b/lib/iris/tests/unit/common/lenient/test_Lenient.py @@ -82,18 +82,32 @@ def test_maths_value_invalid(self): with self.assertRaisesRegex(ValueError, emsg): self.lenient["maths"] = value - def test_maths_disable(self): + def test_maths_disable__lenient_enable_true(self): self.assertTrue(_LENIENT.enable) self.lenient["maths"] = False self.assertFalse(self.lenient.__dict__["maths"]) self.assertFalse(_LENIENT.enable) - def test_maths_enable(self): + def test_maths_disable__lenient_enable_false(self): + _LENIENT.__dict__["enable"] = False + self.assertFalse(_LENIENT.enable) + self.lenient["maths"] = False + self.assertFalse(self.lenient.__dict__["maths"]) + self.assertFalse(_LENIENT.enable) + + def test_maths_enable__lenient_enable_true(self): self.assertTrue(_LENIENT.enable) self.lenient["maths"] = True self.assertTrue(self.lenient.__dict__["maths"]) self.assertTrue(_LENIENT.enable) + def test_maths_enable__lenient_enable_false(self): + _LENIENT.__dict__["enable"] = False + self.assertFalse(_LENIENT.enable) + self.lenient["maths"] = True + self.assertTrue(self.lenient.__dict__["maths"]) + self.assertTrue(_LENIENT.enable) + class Test_context(tests.IrisTest): def setUp(self): @@ -107,10 +121,62 @@ def test_nop(self): self.assertTrue(self.lenient["maths"]) - def test_maths_disable(self): + def test_maths_disable__lenient_true(self): + # synchronised + self.assertTrue(_LENIENT.enable) self.assertTrue(self.lenient["maths"]) with self.lenient.context(maths=False): + # still synchronised + self.assertFalse(_LENIENT.enable) self.assertFalse(self.lenient["maths"]) + # still synchronised + self.assertTrue(_LENIENT.enable) + self.assertTrue(self.lenient["maths"]) + + def test_maths_disable__lenient_false(self): + # not synchronised + _LENIENT.__dict__["enable"] = False + self.assertFalse(_LENIENT.enable) self.assertTrue(self.lenient["maths"]) + + with self.lenient.context(maths=False): + # now synchronised + self.assertFalse(_LENIENT.enable) + self.assertFalse(self.lenient["maths"]) + + # still synchronised + self.assertTrue(_LENIENT.enable) + self.assertTrue(self.lenient["maths"]) + + def test_maths_enable__lenient_true(self): + # not synchronised + self.assertTrue(_LENIENT.enable) + self.lenient.__dict__["maths"] = False + self.assertFalse(self.lenient["maths"]) + + with self.lenient.context(maths=True): + # now synchronised + self.assertTrue(_LENIENT.enable) + self.assertTrue(self.lenient["maths"]) + + # still synchronised + self.assertFalse(_LENIENT.enable) + self.assertFalse(self.lenient["maths"]) + + def test_maths_enable__lenient_false(self): + # synchronised + _LENIENT.__dict__["enable"] = False + self.assertFalse(_LENIENT.enable) + self.lenient.__dict__["maths"] = False + self.assertFalse(self.lenient["maths"]) + + with self.lenient.context(maths=True): + # still synchronised + self.assertTrue(_LENIENT.enable) + self.assertTrue(self.lenient["maths"]) + + # still synchronised + self.assertFalse(_LENIENT.enable) + self.assertFalse(self.lenient["maths"])