Repository: NCAS-CMS/cfdm

Issue:
`cf.Field.cell_methods` returns incorrect results when an invalid identifer is provided
At cfdm `v1.11.1.0`, when an invalid selection is passed to `cfdm.Field.cell_methods`, _all_ cell method constructs are returned, rather than none of them as expected:
```python
>>> import cfdm
>>> f = cfdm.example_field(1)
>>> print(f.cell_methods())  # CORRECT output:
Constructs:
{'cellmethod0': <CellMethod: domainaxis1: domainaxis2: mean where land (interval: 0.1 degrees)>,
 'cellmethod1': <CellMethod: domainaxis3: maximum>}
>>> print(f.cell_methods('bad identifier'))  # WRONG output, expected: Constructs: {}
Constructs:
{'cellmethod0': <CellMethod: domainaxis1: domainaxis2: mean where land (interval: 0.1 degrees)>,
 'cellmethod1': <CellMethod: domainaxis3: maximum>}
```

Discussion:

Solution patch:
diff --git a/Changelog.rst b/Changelog.rst
index 522654d30..5b47696a5 100644
--- a/Changelog.rst
+++ b/Changelog.rst
@@ -3,6 +3,9 @@ Version NEXTVERSION
 
 **2024-??-??**
 
+* Fix bug that returned incorrect results when an invalid identifer is
+  provided to `cf.Field.cell_methods`
+  (https://github.com/NCAS-CMS/cfdm/issues/299)
 * Upgrades to allow cfdm to work with Python 3.12
   (https://github.com/NCAS-CMS/cfdm/issues/302)
 * Extension to the HDF5 chunks API
diff --git a/cfdm/field.py b/cfdm/field.py
index 4ddddf29f..b64c19de0 100644
--- a/cfdm/field.py
+++ b/cfdm/field.py
@@ -664,6 +664,18 @@ def cell_methods(self, *identities, **filter_kwargs):
 
         **Examples**
 
+        >>> f = {{package}}.example_field(1)
+        >>> print(f.cell_methods())
+        Constructs:
+        {'cellmethod0': <CellMethod: domainaxis1: domainaxis2: mean where land (interval: 0.1 degrees)>,
+         'cellmethod1': <CellMethod: domainaxis3: maximum>}
+        >>> print(f.cell_methods('time'))
+        Constructs:
+        {'cellmethod1': <CellMethod: domainaxis3: maximum>}
+        >>> print(f.cell_methods('bad identifier'))
+        Constructs:
+        {}
+
         """
         cached = filter_kwargs.get("cached")
         if cached is not None:
@@ -704,6 +716,13 @@ def cell_methods(self, *identities, **filter_kwargs):
                         keys.add(cm_key)
 
             identities = ()
+            if not keys:
+                # Specify a key of None to ensure that no cell methods
+                # are selected. (If keys is an empty set then all cell
+                # methods are selected, which is not what we want,
+                # here.)
+                keys = (None,)
+
             filter_kwargs = {
                 "filter_by_key": keys,
                 "todict": filter_kwargs.pop("todict", False),


Test patch:
diff --git a/cfdm/test/test_Field.py b/cfdm/test/test_Field.py
index 3bd52cf3a..1250b9173 100644
--- a/cfdm/test/test_Field.py
+++ b/cfdm/test/test_Field.py
@@ -355,6 +355,10 @@ def test_Field_CONSTRUCTS(self):
         for key, value in constructs.items():
             self.assertIsInstance(value, cfdm.CellMethod)
 
+        # Check that no cell methods are returned when a bad
+        # identifier is provided
+        self.assertFalse(f.cell_methods("bad identifier"))
+
         constructs = f.coordinate_references()
         n = 2
         self.assertEqual(len(constructs), n)