diff --git a/cf_units/tests/integration/parse/test_graph.py b/cf_units/tests/integration/parse/test_graph.py index 9743b8a0..fb1ad67f 100644 --- a/cf_units/tests/integration/parse/test_graph.py +++ b/cf_units/tests/integration/parse/test_graph.py @@ -2,6 +2,7 @@ # # 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. +# ruff: noqa: E402 import pytest antlr4 = pytest.importorskip("antlr4") diff --git a/cf_units/tests/integration/parse/test_parse.py b/cf_units/tests/integration/parse/test_parse.py index b76fadbf..5e91fd5b 100644 --- a/cf_units/tests/integration/parse/test_parse.py +++ b/cf_units/tests/integration/parse/test_parse.py @@ -2,6 +2,7 @@ # # 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. +# ruff: noqa: E402 import pytest antlr4 = pytest.importorskip("antlr4") diff --git a/cf_units/tests/test_coding_standards.py b/cf_units/tests/test_coding_standards.py index dce86a11..e0caa0fb 100644 --- a/cf_units/tests/test_coding_standards.py +++ b/cf_units/tests/test_coding_standards.py @@ -128,7 +128,6 @@ def test_python_versions(): # 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" @@ -137,17 +136,17 @@ def test_python_versions(): text_searches: list[tuple[Path, str]] = [ ( pyproject_toml_file, - f'target-version = "py{supported_strip[0]}"', - ), - ( - setup_cfg_file, "\n ".join( [ - f"Programming Language :: Python :: {ver}" + f'"Programming Language :: Python :: {ver}",' for ver in supported ] ), ), + ( + pyproject_toml_file, + f'requires-python = ">={supported[0]}"', + ), ( tox_file, "[testenv:py{" + ",".join(supported_strip) + "}-lock]", diff --git a/cf_units/tests/test_tex.py b/cf_units/tests/test_tex.py index 966c887a..ffb91b6e 100644 --- a/cf_units/tests/test_tex.py +++ b/cf_units/tests/test_tex.py @@ -2,6 +2,7 @@ # # 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. +# ruff: noqa: E402 import pytest antlr4 = pytest.importorskip("antlr4") diff --git a/pyproject.toml b/pyproject.toml index ce92d7bd..c3d4cb16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,11 +5,71 @@ requires = [ "setuptools_scm[toml]>=7.0", "wheel", "oldest-supported-numpy", - "Cython>=3.0" + "Cython>=3.0", ] # Defined by PEP 517 build-backend = "setuptools.build_meta" +[project] +name = "cf-units" +authors = [ + {name = "cf-units Contributors", email = "scitools.pub@gmail.com"} +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Atmospheric Science", + "Topic :: Scientific/Engineering :: Oceanography", +] +dependencies = [ + "cftime >=1.2", + "jinja2", + "numpy <2" + # udunits2 cannot be installed with pip, and it is expected to be + # installed separately. +] +description = "Units of measure as required by the Climate and Forecast (CF) metadata conventions" +dynamic = [ + "version", +] +keywords = [ + "units", + "cf", + "cf-metadata", + "netcdf", + "science", + "earth-science", + "oceanography", + "meteorology", + "climate", + "python", +] +license.file = "LICENSE" +readme = "README.md" +requires-python = ">=3.10" + +[project.optional-dependencies] +all = ["cf-units[latex]", "cf-units[docs]", "cf-units[test]"] +# To update antlr: see cf_units/_udunits2_parser/README.md" +latex = ["antlr4-python3-runtime ==4.11.1"] +docs = ["sphinx"] +test = ["codecov", "cython", "jinja2", "pip", "pytest", "pytest-cov"] + +[project.urls] +Code = "https://github.com/SciTools/cf-units" +Discussions = "https://github.com/SciTools/cf-units/discussions" +Issues = "https://github.com/SciTools/cf-units/issues" +Documentation = "https://cf-units.readthedocs.io" + [tool.coverage.run] branch = true plugins = [ @@ -41,6 +101,9 @@ doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS NUMBER" minversion = "6.0" testpaths = "cf_units" +[tool.setuptools.packages.find] +include = ["cf_units"] + [tool.setuptools_scm] write_to = "cf_units/_version.py" local_scheme = "dirty-tag" @@ -51,7 +114,6 @@ exclude = [ "cf_units/_udunits2_parser/parser", ] line-length = 79 -target-version = "py310" [tool.ruff.lint] select = [ diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index deb05e84..00000000 --- a/setup.cfg +++ /dev/null @@ -1,67 +0,0 @@ -[metadata] -author = SciTools Developers -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Science/Research - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python :: 3 :: Only - 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 -keywords = - units - cf - netcdf - science - oceanography - meteorology - climate -license = BSD -license_files = LICENSE -long_description = file: README.md -long_description_content_type = text/markdown -name = cf-units -project_urls = - Code = https://github.com/SciTools/cf-units - Discussions = https://github.com/SciTools/cf-units/discussions - Issues = https://github.com/SciTools/cf-units/issues -url = https://github.com/SciTools/cf-units -version = attr: cf_units.__version__ - -[options] -include_package_data = True -install_requires = - cftime >=1.2 - jinja2 - numpy ==1.26.4 - # udunits2 cannot be installed with pip, and it is expected to be - # installed separately. -packages = find_namespace: -python_requires = - >=3.9 -zip_safe = False - -[options.extras_require] -latex = - antlr4-python3-runtime ==4.11.1 # To update this, see cf_units/_udunits2_parser/README.md -docs = - sphinx -test = - codecov - cython - jinja2 - pip - pytest - pytest-cov -all = - pre-commit - %(latex)s - %(docs)s - %(test)s - -[aliases] -test = pytest diff --git a/setup.py b/setup.py index 95a90521..2f7c3587 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,8 @@ +"""Setup routines to enable cf-units' Cython elements. + +All other setup configuration is in `pyproject.toml`. +""" + import sys from distutils.sysconfig import get_config_var from os import environ @@ -6,7 +11,8 @@ from setuptools import Command, Extension, setup -# Default to using cython, but use the .c files if it doesn't exist +# Default to using cython, but use the .c files if it doesn't exist. +# Supports the widest possible range of developer setups. try: from Cython.Build import cythonize except ImportError: @@ -40,27 +46,14 @@ def run(self): path.unlink() -def get_include_dirs(): - include_dirs = [] - include_dir = environ.get("UDUNITS2_INCDIR") - if include_dir is None: - include_dir = get_config_var("INCLUDEDIR") - if include_dir is not None: - include_dirs.append(include_dir) - return include_dirs - - -def get_library_dirs(): - library_dirs = [] - library_dir = environ.get("UDUNITS2_LIBDIR") - if library_dir is None: - library_dir = get_config_var("LIBDIR") - if library_dir is not None: - library_dirs.append(library_dir) - return library_dirs +def get_dirs(env_var: str, config_var: str): + """Get a directory from an env variable or a distutils config variable.""" + result = environ.get(env_var) or get_config_var(config_var) + return [result] if result else [] def get_package_data(): + """Find and correctly package the UDUNITS2 XML files for a wheel build.""" package_data = {} # Determine whether we're building a wheel. if "bdist_wheel" in sys.argv: @@ -99,6 +92,7 @@ def get_package_data(): def numpy_build_ext(pars): + """Make the NumPy headers available for the Cython layer.""" from setuptools.command.build_ext import build_ext as _build_ext class build_ext(_build_ext): @@ -126,12 +120,16 @@ def _set_builtin(name, value): sys.argv.remove(FLAG_COVERAGE) print('enable: "linetrace" Cython compiler directive') -library_dirs = get_library_dirs() +include_dirs = get_dirs("UDUNITS2_INCDIR", "INCLUDEDIR") +library_dirs = get_dirs("UDUNITS2_LIBDIR", "LIBDIR") +# Some of the complexity MUST remain in setup.py due to its dynamic nature. To +# reduce confusion, the Extension is 100% defined here, rather than splitting +# between setup.py and pyproject.toml `ext-modules`. udunits_ext = Extension( f"{PACKAGE}._udunits2", [str(Path(f"{PACKAGE}") / f"_udunits2.{'pyx' if cythonize else 'c'}")], - include_dirs=get_include_dirs(), + include_dirs=include_dirs, library_dirs=library_dirs, libraries=["udunits2"], define_macros=DEFINE_MACROS, @@ -141,6 +139,7 @@ def _set_builtin(name, value): ) if cythonize: + # https://docs.cython.org/en/latest/src/userguide/source_files_and_compilation.html#distributing-cython-modules [udunits_ext] = cythonize( udunits_ext, compiler_directives=COMPILER_DIRECTIVES,