Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fully support Python 3.12 #461

Merged
merged 10 commits into from
Sep 20, 2024
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
Loading