Repository: NCAS-CMS/cf-python

Issue:
`TypeError` is raised when using the `equal` or `exist` keyword to `cf.aggregate`
`TypeError` is raised when using the `equal` or `exist` keyword to `cf.aggregate`:

```python
>>> import cf
>>> f = cf.example_field(0)
>>> g = cf.aggregate([f[:2], f[2:]], equal=["standard_name"])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
----> 1 g = cf.aggregate([a, b, c, d], equal=["foo"])

File ~/miniconda3/lib/python3.9/site-packages/cfdm/decorators.py:171, in _manage_log_level_via_verbosity.<locals>.verbose_override_wrapper(*args, **kwargs)
    168 # After method completes, re-set any changes to log level or
    169 # enabling
    170 try:
--> 171     return method_with_verbose_kwarg(*args, **kwargs)
    172 except Exception:
    173     raise

File ~/miniconda3/lib/python3.9/site-packages/cf/aggregate.py:1717, in aggregate(fields, verbose, relaxed_units, overlap, contiguous, relaxed_identities, ncvar_identities, respect_valid, equal_all, exist_all, equal, exist, ignore, exclude, dimension, concatenate, copy, axes, donotchecknonaggregatingaxes, allow_no_identity, atol, rtol, no_overlap, shared_nc_domain, field_identity, info)
   1711 signatures = {}
   1712 for f in flat(fields):
   1713     # ------------------------------------------------------------
   1714     # Create the metadata summary, including the structural
   1715     # signature
   1716     # ------------------------------------------------------------
-> 1717     meta = _Meta(
   1718         f,
   1719         verbose=verbose,
   1720         rtol=rtol,
   1721         atol=atol,
   1722         relaxed_units=relaxed_units,
   1723         allow_no_identity=allow_no_identity,
   1724         equal_all=equal_all,
   1725         exist_all=exist_all,
   1726         equal=equal,
   1727         exist=exist,
   1728         ignore=ignore,
   1729         dimension=dimension,
   1730         relaxed_identities=relaxed_identities,
   1731         ncvar_identities=ncvar_identities,
   1732         field_identity=field_identity,
   1733         respect_valid=respect_valid,
   1734         copy=copy,
   1735     )
   1737     if not meta:
   1738         unaggregatable = True

File ~/miniconda3/lib/python3.9/site-packages/cf/aggregate.py:687, in _Meta.__init__(self, f, rtol, atol, verbose, relaxed_units, allow_no_identity, respect_valid, equal_all, exist_all, equal, exist, ignore, dimension, relaxed_identities, ncvar_identities, field_identity, copy)
    685 else:
    686     properties = f.properties()
--> 687     for p in ignore:
    688         properties.pop(p, None)
    690     if equal:

TypeError: 'NoneType' object is not iterable
```
This incorrect behaviour is the same for `exist` keyword.

Discussion:

Patch:
diff --git a/Changelog.rst b/Changelog.rst
index a55394508..c274a182c 100644
--- a/Changelog.rst
+++ b/Changelog.rst
@@ -6,6 +6,9 @@ version 3.14.0
 * New method: `cf.Field.get_original_filenames`
   (https://github.com/NCAS-CMS/cf-python/issues/448)
 * New method: `cf.Field.to_dask_array`
+* Fixed bug that raised an exception when using the ``equal`` or
+  ``exist`` keyword of `cf.aggregate`
+  (https://github.com/NCAS-CMS/cf-python/issues/499)
 * Changed dependency: ``1.10.0.1<=cfdm<1.10.1.0``
 * New dependency: ``dask>=2022.6.0``
 
@@ -18,8 +21,8 @@ version 3.13.1
 
 * Upgrade to allow cf to work with Python 3.10
   (https://github.com/NCAS-CMS/cf-python/issues/419)
-* Fix bug when setting coordinate bounds with different units to their
-  parent coordinates
+* Fixed bug when setting coordinate bounds with different units to
+  their parent coordinates
   (https://github.com/NCAS-CMS/cf-python/issues/417)
 * Fixed bug that created incorrect hybrid height levels when reading
   UM fields that also have pseudolevels
@@ -35,10 +38,10 @@ version 3.13.0
 
 **2022-06-23**
 
-* Fix bug in `cf.read` when reading PP or fields files for which the
+* Fixed bug in `cf.read` when reading PP or fields files for which the
   ``um`` keyword has been set, but without the ``'version'`` key
   (https://github.com/NCAS-CMS/cf-python/issues/306)
-* Fix bug when setting the CFA "base" option to an empty string in
+* Fixed bug when setting the CFA "base" option to an empty string in
   `cf.write` (https://github.com/NCAS-CMS/cf-python/issues/346)
 * Fixed failure from `cf.write` when writing identical (auxiliary)
   coordinates to different data variables in different groups
diff --git a/cf/aggregate.py b/cf/aggregate.py
index c349e0cee..ce165009d 100644
--- a/cf/aggregate.py
+++ b/cf/aggregate.py
@@ -684,8 +684,9 @@ class _Meta:
             self.properties = ()
         else:
             properties = f.properties()
-            for p in ignore:
-                properties.pop(p, None)
+            if ignore:
+                for p in ignore:
+                    properties.pop(p, None)
 
             if equal:
                 eq = dict(
diff --git a/cf/field.py b/cf/field.py
index faa2f6932..76c5e0160 100644
--- a/cf/field.py
+++ b/cf/field.py
@@ -4554,9 +4554,6 @@ class Field(mixin.FieldDomain, mixin.PropertiesData, cfdm.Field):
     def title(self):
         self.del_property("title")
 
-    # ----------------------------------------------------------------
-    # Methods
-    # ----------------------------------------------------------------
     def cell_area(
         self,
         radius="earth",


Test patch:
diff --git a/cf/test/test_aggregate.py b/cf/test/test_aggregate.py
index cbddce48b..83ea74ef6 100644
--- a/cf/test/test_aggregate.py
+++ b/cf/test/test_aggregate.py
@@ -279,6 +279,62 @@ class aggregateTest(unittest.TestCase):
         )
         self.assertEqual(len(a), 1)
 
+    def test_aggregate_equal_equal_all(self):
+        f = cf.example_field(0)
+        f.set_property("foo", "bar")
+        a, b, c, d = f[0], f[1], f[2:4], f[4]
+
+        c.set_property("foo", "baz")
+        d.set_property("foo", "baz")
+
+        g = cf.aggregate([a, b, c, d])
+        self.assertEqual(len(g), 1)
+
+        g = cf.aggregate([a, b, c, d], equal=["foo"])
+        self.assertEqual(len(g), 2)
+
+        g = cf.aggregate([a, b, c, d], equal_all=True)
+        self.assertEqual(len(g), 2)
+
+        d.del_property("foo")
+
+        g = cf.aggregate([a, b, c, d])
+        self.assertEqual(len(g), 1)
+
+        g = cf.aggregate([a, b, c, d], equal=["foo"])
+        self.assertEqual(len(g), 3)
+
+        g = cf.aggregate([a, b, c, d], equal_all=True)
+        self.assertEqual(len(g), 3)
+
+    def test_aggregate_exist_exist_all(self):
+        f = cf.example_field(0)
+        f.set_property("foo", "bar")
+        a, b, c, d = f[0], f[1], f[2:4], f[4]
+
+        c.set_property("foo", "baz")
+        d.set_property("foo", "baz")
+
+        g = cf.aggregate([a, b, c, d])
+        self.assertEqual(len(g), 1)
+
+        g = cf.aggregate([a, b, c, d], exist=["foo"])
+        self.assertEqual(len(g), 1)
+
+        g = cf.aggregate([a, b, c, d], exist_all=True)
+        self.assertEqual(len(g), 1)
+
+        d.del_property("foo")
+
+        g = cf.aggregate([a, b, c, d])
+        self.assertEqual(len(g), 1)
+
+        g = cf.aggregate([a, b, c, d], exist=["foo"])
+        self.assertEqual(len(g), 2)
+
+        g = cf.aggregate([a, b, c, d], exist_all=True)
+        self.assertEqual(len(g), 2)
+
 
 if __name__ == "__main__":
     print("Run date:", datetime.datetime.now())