Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/run-test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
# Skip older ubuntu-16.04 & macos-10.15 to save usage resource
os: [ubuntu-latest, macos-latest]
# Note: keep versions quoted as strings else 3.10 taken as 3.1, etc.
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]

# Run on new and old(er) versions of the distros we support (Linux, Mac OS)
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -138,7 +138,7 @@ jobs:

# For one job only, generate a coverage report:
- name: Upload coverage report to Codecov
# Get coverage from only one job (choose with Ubuntu Python 3.8 as
# Get coverage from only one job (choose with Ubuntu Python 3.10 as
# representative). Note that we could use a separate workflow
# to setup Codecov reports, but given the amount of steps required to
# install including dependencies via conda, that a separate workflow
Expand All @@ -147,7 +147,7 @@ jobs:
# passing at least for that job, avoiding useless coverage reports.
uses: codecov/codecov-action@v3
if: |
matrix.os == "ubuntu-latest" && matrix.python-version == "3.9"
matrix.os == "ubuntu-latest" && matrix.python-version == "3.10"
with:
file: |
${{ github.workspace }}/main/cf/test/cf_coverage_reports/coverage.xml
Expand Down
9 changes: 9 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
Version NEXTVERSION
--------------

**2025-10-??**

* Python 3.9 support removed
(https://github.com/NCAS-CMS/cf-python/issues/896)
* Changed dependency: ``Python>=3.10.0``

Version 3.18.1
--------------

Expand Down
2 changes: 1 addition & 1 deletion cf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@
f"Got {scipy.__version__} at {scipy.__file__}"
)

_minimum_vn = "3.9.0"
_minimum_vn = "3.10.0"
if Version(python_version()) < Version(_minimum_vn):
raise ValueError(
f"Bad python version: cf requires python>={_minimum_vn}. "
Expand Down
187 changes: 92 additions & 95 deletions cf/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,122 +959,119 @@ def _evaluate(self, x, parent_attr):
operator = self._operator
value = self._value

# TODO: Once Python 3.9 is no longer supported, this is a good
# candidate for PEP 622 – Structural Pattern Matching
# (https://peps.python.org/pep-0622)
match operator:
case "gt":
Comment on lines +962 to +963
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful 🥹

_gt = getattr(x, "__query_gt__", None)
if _gt is not None:
return _gt(value)

if operator == "gt":
_gt = getattr(x, "__query_gt__", None)
if _gt is not None:
return _gt(value)
return x > value

return x > value
case "wi":
_wi = getattr(x, "__query_wi__", None)
if _wi is not None:
return _wi(value, self.open_lower, self.open_upper)

if operator == "wi":
_wi = getattr(x, "__query_wi__", None)
if _wi is not None:
return _wi(value, self.open_lower, self.open_upper)
if self.open_lower:
lower_bound = x > value[0]
else:
lower_bound = x >= value[0]

if self.open_lower:
lower_bound = x > value[0]
else:
lower_bound = x >= value[0]
if self.open_upper:
upper_bound = x < value[1]
else:
upper_bound = x <= value[1]

if self.open_upper:
upper_bound = x < value[1]
else:
upper_bound = x <= value[1]
return lower_bound & upper_bound

return lower_bound & upper_bound
case "eq":
try:
return bool(value.search(x))
except AttributeError:
return x == value
except TypeError:
raise ValueError(
"Can't perform regular expression search on a "
f"non-string: {x!r}"
)

if operator == "eq":
try:
return bool(value.search(x))
except AttributeError:
return x == value
except TypeError:
raise ValueError(
"Can't perform regular expression search on a "
f"non-string: {x!r}"
)
case "isclose":
rtol = self.rtol
atol = self.atol
if atol is None:
atol = cf_atol().value

if operator == "isclose":
rtol = self.rtol
atol = self.atol
if atol is None:
atol = cf_atol().value
if rtol is None:
rtol = cf_rtol().value

if rtol is None:
rtol = cf_rtol().value
_isclose = getattr(x, "__query_isclose__", None)
if _isclose is not None:
return _isclose(value, rtol, atol)

_isclose = getattr(x, "__query_isclose__", None)
if _isclose is not None:
return _isclose(value, rtol, atol)
return np.isclose(x, value, rtol=rtol, atol=atol)

return np.isclose(x, value, rtol=rtol, atol=atol)

if operator == "ne":
try:
return not bool(value.search(x))
except AttributeError:
return x != value
except TypeError:
raise ValueError(
"Can't perform regular expression search on a "
f"non-string: {x!r}"
)
case "ne":
try:
return not bool(value.search(x))
except AttributeError:
return x != value
except TypeError:
raise ValueError(
"Can't perform regular expression search on a "
f"non-string: {x!r}"
)

if operator == "lt":
_lt = getattr(x, "__query_lt__", None)
if _lt is not None:
return _lt(value)
case "lt":
_lt = getattr(x, "__query_lt__", None)
if _lt is not None:
return _lt(value)

return x < value
return x < value

if operator == "le":
_le = getattr(x, "__query_le__", None)
if _le is not None:
return _le(value)
case "le":
_le = getattr(x, "__query_le__", None)
if _le is not None:
return _le(value)

return x <= value
return x <= value

if operator == "ge":
_ge = getattr(x, "__query_ge_", None)
if _ge is not None:
return _ge(value)
case "ge":
_ge = getattr(x, "__query_ge_", None)
if _ge is not None:
return _ge(value)

return x >= value
return x >= value

if operator == "wo":
_wo = getattr(x, "__query_wo__", None)
if _wo is not None:
return _wo(value)
case "wo":
_wo = getattr(x, "__query_wo__", None)
if _wo is not None:
return _wo(value)

return (x < value[0]) | (x > value[1])
return (x < value[0]) | (x > value[1])

if operator == "set":
if isinstance(x, str):
for v in value:
try:
if v.search(x):
return True
except AttributeError:
if x == v:
return True
case "set":
if isinstance(x, str):
for v in value:
try:
if v.search(x):
return True
except AttributeError:
if x == v:
return True

return False
else:
_set = getattr(x, "__query_set__", None)
if _set is not None:
return _set(value)

i = iter(value)
v = next(i)
out = x == v
for v in i:
out |= x == v

return out
return False
else:
_set = getattr(x, "__query_set__", None)
if _set is not None:
return _set(value)

i = iter(value)
v = next(i)
out = x == v
for v in i:
out |= x == v

return out

def inspect(self):
"""Inspect the object for debugging.
Expand Down
4 changes: 3 additions & 1 deletion cf/test/test_Data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4592,7 +4592,9 @@ def test_Data_masked_values(self):
d = cf.Data(array)
e = d.masked_values(1.1)
ea = e.array
a = np.ma.masked_values(array, 1.1, rtol=cf.rtol(), atol=cf.atol())
a = np.ma.masked_values(
array, 1.1, rtol=float(cf.rtol()), atol=float(cf.atol())
)
self.assertTrue(np.isclose(ea, a).all())
self.assertTrue((ea.mask == a.mask).all())
self.assertIsNone(d.masked_values(1.1, inplace=True))
Expand Down
1 change: 0 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import cf


print("\ncf environment:")
print("-----------------")
cf.environment()
Expand Down
4 changes: 2 additions & 2 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Windows Subsystem for Linux (WSL)
**Python versions**
-------------------

The cf package is only for Python 3.9 or newer.
The cf package is only for Python 3.10 or newer.

----

Expand Down Expand Up @@ -209,7 +209,7 @@ installed, which:
Required
^^^^^^^^

* `Python <https://www.python.org/>`_, 3.9.0 or newer.
* `Python <https://www.python.org/>`_, 3.10.0 or newer.

* `numpy <http://www.numpy.org>`_, versions 2.0.0 or newer.

Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ def compile():
"Operating System :: MacOS",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down
Loading