Skip to content

Commit

Permalink
Fully support Python 3.12 (#461)
Browse files Browse the repository at this point in the history
* Use pathlib in test_coding_standards.

* Introduce test_python_versions, including deliberate rollbacks.

* Roll forward Python to 10, 11, 12 in all files.

* Update test_python_versions for macos support.

* Attempt better directory finding.

* Better handling if not running on a git repo.

* Update conda_spec for Tox.

* Catch inconsistencies between tox header and conda_spec.
  • Loading branch information
trexfeathers authored Sep 20, 2024
1 parent 674146d commit 5b5cb1c
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 40 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-locks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
ENV_NAME: "ci-locks"
strategy:
matrix:
lock: [py39-lock, py310-lock, py311-lock]
lock: [py310-lock, py311-lock, py312-lock]
steps:
- name: "Checkout"
uses: actions/checkout@v4
Expand All @@ -54,7 +54,7 @@ jobs:
miniforge-version: latest
channels: conda-forge,defaults
activate-environment: ${{ env.ENV_NAME }}
auto-update-conda: true
auto-update-conda: true
use-only-tar-bz2: true

- name: "Conda environment cache"
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
version: [py310, py311]
version: [py310, py311, py312]
gitpath-prepend: [""]
include:
- os: ubuntu-latest
platform: linux
- os: ubuntu-latest
version: py311
version: py312
posargs: "--cov-report=xml --cov"
post-command: codecov
- os: macos-latest
version: py311
version: py312
platform: osx
# On macos, the up-to-date git may not be first on the path
# N.B. setting includes a final ":", to simplify the path setting command
Expand Down
132 changes: 104 additions & 28 deletions cf_units/tests/test_coding_standards.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
# This file is part of cf-units and is released under the BSD license.
# See LICENSE in the root of the repository for full licensing details.

import os
import subprocess
from datetime import datetime
from fnmatch import fnmatch
from glob import glob
from pathlib import Path

import pytest

Expand All @@ -19,21 +18,18 @@
# See LICENSE in the root of the repository for full licensing details."""


# Guess cf_units repo directory of cf_units - realpath is used to mitigate
# against Python finding the cf_units package via a symlink.
DIR = os.path.realpath(os.path.dirname(cf_units.__file__))
REPO_DIR = os.path.dirname(DIR)
DOCS_DIR = os.path.join(REPO_DIR, "doc")
DOCS_DIR = cf_units.config.get_option("Resources", "doc_dir", default=DOCS_DIR)
REPO_DIR = Path(__file__).resolve().parents[2]
DOCS_DIR = REPO_DIR / "docs"
DOCS_DIR = Path(
cf_units.config.get_option("Resources", "doc_dir", default=DOCS_DIR)
)
exclusion = ["Makefile", "make.bat", "build"]
DOCS_DIRS = glob(os.path.join(DOCS_DIR, "*"))
DOCS_DIRS = [
DOC_DIR
for DOC_DIR in DOCS_DIRS
if os.path.basename(DOC_DIR) not in exclusion
]
DOCS_DIRS = DOCS_DIR.glob("*")
DOCS_DIRS = [DOC_DIR for DOC_DIR in DOCS_DIRS if DOC_DIR.name not in exclusion]
IS_GIT_REPO = (REPO_DIR / ".git").is_dir()


@pytest.mark.skipif(not IS_GIT_REPO, reason="Not a git repository.")
class TestLicenseHeaders:
@staticmethod
def whatchanged_parse(whatchanged_output):
Expand Down Expand Up @@ -73,10 +69,6 @@ def last_change_by_fname():
or cannot be found by subprocess, an IOError may also be raised.
"""
# Check the ".git" folder exists at the repo dir.
if not os.path.isdir(os.path.join(REPO_DIR, ".git")):
raise ValueError("{} is not a git repository.".format(REPO_DIR))

# Call "git whatchanged" to get the details of all the files and when
# they were last changed.
output = subprocess.check_output(
Expand All @@ -100,20 +92,14 @@ def test_license_headers(self):
"cf_units/_udunits2_parser/parser/*",
)

try:
last_change_by_fname = self.last_change_by_fname()
except ValueError:
# Caught the case where this is not a git repo.
return pytest.skip(
"cf_units installation did not look like a " "git repo."
)
last_change_by_fname = self.last_change_by_fname()

failed = False
for fname, last_change in sorted(last_change_by_fname.items()):
full_fname = os.path.join(REPO_DIR, fname)
full_fname = REPO_DIR / fname
if (
full_fname.endswith(".py")
and os.path.isfile(full_fname)
full_fname.suffix == ".py"
and full_fname.is_file()
and not any(fnmatch(fname, pat) for pat in exclude_patterns)
):
with open(full_fname) as fh:
Expand All @@ -129,3 +115,93 @@ def test_license_headers(self):
raise AssertionError(
"There were license header failures. See stdout."
)


@pytest.mark.skipif(not IS_GIT_REPO, reason="Not a git repository.")
def test_python_versions():
"""Confirm alignment of ALL files listing supported Python versions."""
supported = ["3.10", "3.11", "3.12"]
supported_strip = [ver.replace(".", "") for ver in supported]
supported_latest = supported_strip[-1]

workflows_dir = REPO_DIR / ".github" / "workflows"

# Places that are checked:
pyproject_toml_file = REPO_DIR / "pyproject.toml"
setup_cfg_file = REPO_DIR / "setup.cfg"
tox_file = REPO_DIR / "tox.ini"
ci_locks_file = workflows_dir / "ci-locks.yml"
ci_tests_file = workflows_dir / "ci-tests.yml"
ci_wheels_file = workflows_dir / "ci-wheels.yml"

text_searches: list[tuple[Path, str]] = [
(
pyproject_toml_file,
"target-version = ["
+ ", ".join([f'"py{p}"' for p in supported_strip])
+ "]",
),
(
setup_cfg_file,
"\n ".join(
[
f"Programming Language :: Python :: {ver}"
for ver in supported
]
),
),
(
tox_file,
"[testenv:py{" + ",".join(supported_strip) + "}-lock]",
),
(
tox_file,
"[testenv:py{"
+ ",".join(supported_strip)
+ "}-{linux,osx,win}-test]",
),
(
ci_locks_file,
"lock: ["
+ ", ".join([f"py{p}-lock" for p in supported_strip])
+ "]",
),
(
ci_tests_file,
(
f"os: [ubuntu-latest]\n"
f"{8*' '}version: ["
+ ", ".join([f"py{p}" for p in supported_strip])
+ "]"
),
),
(
ci_tests_file,
(f"os: ubuntu-latest\n" f"{12*' '}version: py{supported_latest}"),
),
(
ci_tests_file,
(f"os: macos-latest\n" f"{12*' '}version: py{supported_latest}"),
),
]

# This routine will not check for file existence first - if files are
# being added/removed we want developers to be aware that this test will
# need to be updated.
for path, search in text_searches:
assert search in path.read_text()

tox_text = tox_file.read_text()
for version in supported_strip:
# A fairly lazy implementation, but should catch times when the section
# header does not match the conda_spec for the `tests` section.
# (Note that Tox does NOT provide its own helpful error in these cases).
py_version = f"py{version}"
assert tox_text.count(f" {py_version}-") == 3
assert tox_text.count(f"{py_version}-lock") == 3

ci_wheels_text = ci_wheels_file.read_text()
(cibw_line,) = [
line for line in ci_wheels_text.splitlines() if "CIBW_SKIP" in line
]
assert all([p not in cibw_line for p in supported_strip])
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ local_scheme = "dirty-tag"

[tool.black]
line-length = 79
target-version = ["py39", "py310", "py311"]
target-version = ["py310", "py311", "py312"]
include = '\.pyi?$'
exclude = '''
(
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ classifiers =
License :: OSI Approved :: BSD License
Operating System :: OS Independent
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
Topic :: Scientific/Engineering
description = Units of measure as required by the Climate and Forecast (CF) metadata conventions
download_url = https://github.com/SciTools/cf-units
Expand Down
10 changes: 5 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
requires =
tox-conda
tox-run-command


[testenv:py{310,311,312}-lock]
allowlist_externals =
cp
Expand Down Expand Up @@ -34,15 +34,15 @@ commands =

[testenv:py{310,311,312}-{linux,osx,win}-test]
conda_spec =
py39-linux: {toxinidir}{/}requirements{/}locks{/}py39-lock-linux-64.txt
py310-linux: {toxinidir}{/}requirements{/}locks{/}py310-lock-linux-64.txt
py311-linux: {toxinidir}{/}requirements{/}locks{/}py311-lock-linux-64.txt
py39-osx: {toxinidir}{/}requirements{/}locks{/}py39-lock-osx-64.txt
py312-linux: {toxinidir}{/}requirements{/}locks{/}py312-lock-linux-64.txt
py310-osx: {toxinidir}{/}requirements{/}locks{/}py310-lock-osx-64.txt
py311-osx: {toxinidir}{/}requirements{/}locks{/}py311-lock-osx-64.txt
py39-win: {toxinidir}{/}requirements{/}locks{/}py39-lock-win-64.txt
py312-osx: {toxinidir}{/}requirements{/}locks{/}py312-lock-osx-64.txt
py310-win: {toxinidir}{/}requirements{/}locks{/}py310-lock-win-64.txt
py311-win: {toxinidir}{/}requirements{/}locks{/}py311-lock-win-64.txt
py312-win: {toxinidir}{/}requirements{/}locks{/}py312-lock-win-64.txt
description =
Perform cf-units unit/integration tests.
passenv =
Expand Down

0 comments on commit 5b5cb1c

Please sign in to comment.