From c6eb95e84d298e25cd123530263b1521a794901c Mon Sep 17 00:00:00 2001 From: Ken Kroenlein Date: Wed, 1 Oct 2025 19:04:23 -0600 Subject: [PATCH 1/5] Migrate build to pyproject.toml --- .gitignore | 1 + gemd/__version__.py | 2 +- gemd/demo/cake.py | 2 +- gemd/demo/strehlow_and_cook.py | 2 +- gemd/units/impl.py | 58 +++++++++++---------------- pyproject.toml | 73 ++++++++++++++++++++++++++++++++++ requirements.txt | 3 +- setup.py | 67 ------------------------------- tests/units/test_parser.py | 2 +- tox.ini | 6 --- 10 files changed, 102 insertions(+), 114 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.gitignore b/.gitignore index d3acf139..b4a8eafd 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ coverage.xml *.cover .hypothesis/ .pytest_cache/ +prof/ # Translations *.mo diff --git a/gemd/__version__.py b/gemd/__version__.py index 8a124bf6..b19ee4b7 100644 --- a/gemd/__version__.py +++ b/gemd/__version__.py @@ -1 +1 @@ -__version__ = "2.2.0" +__version__ = "2.2.1" diff --git a/gemd/demo/cake.py b/gemd/demo/cake.py index 8586dac2..794744fd 100644 --- a/gemd/demo/cake.py +++ b/gemd/demo/cake.py @@ -1,5 +1,5 @@ """Bake a cake.""" -from importlib_resources import files +from importlib.resources import files from io import BytesIO import random diff --git a/gemd/demo/strehlow_and_cook.py b/gemd/demo/strehlow_and_cook.py index 11e1035d..fe8f662e 100644 --- a/gemd/demo/strehlow_and_cook.py +++ b/gemd/demo/strehlow_and_cook.py @@ -45,7 +45,7 @@ def import_table(filename=SMALL_TABLE): """Return the deserialized JSON table.""" - from importlib_resources import files + from importlib.resources import files import json return json.loads(files("gemd.demo").joinpath(filename).read_text(encoding='utf-8')) diff --git a/gemd/units/impl.py b/gemd/units/impl.py index dfdccf71..9e94ce45 100644 --- a/gemd/units/impl.py +++ b/gemd/units/impl.py @@ -1,23 +1,25 @@ """Implementation of units.""" from deprecation import deprecated import functools -from importlib_resources import files +from importlib.resources import files import os from pathlib import Path import re from tempfile import TemporaryDirectory from typing import Union, List, Tuple, Generator, Any +try: + from typing import TypeAlias # Python 3.10+ +except ImportError: # pragma nocover + from typing_extensions import TypeAlias # Python 3.9 from pint import UnitRegistry, register_unit_format -try: # Pint 0.23 migrated the location of this method, and augmented it - from pint.pint_eval import tokenizer -except ImportError: # pragma: no cover - from pint.compat import tokenizer +from pint.pint_eval import tokenizer from tokenize import NAME, NUMBER, OP, ERRORTOKEN, TokenInfo # alias the error that is thrown when units are incompatible # this helps to isolate the dependence on pint -from pint.errors import DimensionalityError as IncompatibleUnitsError # noqa Import -from pint.errors import UndefinedUnitError, DefinitionSyntaxError # noqa Import +from pint.errors import DimensionalityError as IncompatibleUnitsError +from pint.errors import UndefinedUnitError, DefinitionSyntaxError +from pint.registry import GenericUnitRegistry # Store directories so they don't get auto-cleaned until exit _TEMP_DIRECTORY = TemporaryDirectory() @@ -216,43 +218,29 @@ def _unmangle_scaling(input_string: str) -> str: return input_string -try: # pragma: no cover - # Pint 0.23 modified the preferred way to derive a custom class - # https://pint.readthedocs.io/en/0.23/advanced/custom-registry-class.html - from pint.registry import GenericUnitRegistry - from typing_extensions import TypeAlias +# Standard approach to creating a custom registry class: +# https://pint.readthedocs.io/en/0.23/advanced/custom-registry-class.html - class _ScaleFactorUnit(UnitRegistry.Unit): - """Child class of Units for generating units w/ clean scaling factors.""" +class _ScaleFactorUnit(UnitRegistry.Unit): + """Child class of Units for generating units w/ clean scaling factors.""" - def __format__(self, format_spec): - result = super().__format__(format_spec) - return _unmangle_scaling(result) + def __format__(self, format_spec): + result = super().__format__(format_spec) + return _unmangle_scaling(result) - class _ScaleFactorQuantity(UnitRegistry.Quantity): - """Child class of Quantity for generating units w/ clean scaling factors.""" - pass - - class _ScaleFactorRegistry(GenericUnitRegistry[_ScaleFactorQuantity, _ScaleFactorUnit]): - """UnitRegistry class that uses _GemdUnits.""" +class _ScaleFactorQuantity(UnitRegistry.Quantity): + """Child class of Quantity for generating units w/ clean scaling factors.""" - Quantity: TypeAlias = _ScaleFactorQuantity - Unit: TypeAlias = _ScaleFactorUnit + pass -except ImportError: # pragma: no cover - # https://pint.readthedocs.io/en/0.21/advanced/custom-registry-class.html - class _ScaleFactorUnit(UnitRegistry.Unit): - """Child class of Units for generating units w/ clean scaling factors.""" - def __format__(self, format_spec): - result = super().__format__(format_spec) - return _unmangle_scaling(result) +class _ScaleFactorRegistry(GenericUnitRegistry[_ScaleFactorQuantity, _ScaleFactorUnit]): + """UnitRegistry class that uses _GemdUnits.""" - class _ScaleFactorRegistry(UnitRegistry): - """UnitRegistry class that uses _GemdUnits.""" + Quantity: TypeAlias = _ScaleFactorQuantity + Unit: TypeAlias = _ScaleFactorUnit - _unit_class = _ScaleFactorUnit _REGISTRY: _ScaleFactorRegistry = None # global requires it be defined in this scope diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..94a984f9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,73 @@ +[project] +name = "gemd" +dynamic = ["version"] +dependencies = [ + "pint>=0.24.4,<0.25", + "deprecation>=2.1.0,<3", + "typing_extensions>=4.8,<5; python_version<'3.10'", +] +requires-python = ">=3.9" +authors = [ + {name = "Citrine Informatics"} +] +description = "Python binding for Citrine's GEMD data model" +readme = "README.md" +license-files = ["LICENSE"] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[project.urls] +Homepage = "http://github.com/CitrineInformatics/gemd-python" + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +version = {attr = "gemd.__version__.__version__"} + +[tool.setuptools.packages.find] +where = ["."] +include = ["gemd"] +exclude = ["docs", "tests"] + +[tool.setuptools.package-data] +"gemd.demo" = ["strehlow_and_cook.pif", "strehlow_and_cook_small.pif", "toothpick.jpg"] +"gemd.units" = ["citrine_en.txt", "constants_en.txt"] +"tests.units" = ["test_units.txt"] + + +[dependency-groups] +dev = [ + "flake8==7.0.0", + "flake8-docstrings==1.7.0", + "numpy==1.24.4; python_version<'3.10'", + "pandas==2.0.3; python_version<'3.10'", + "numpy>=2.0.2,<=2.1.0; python_version>='3.10'", + "pandas==2.3.0; python_version>='3.10'", + "pytest==8.0.0", + "pytest-cov==4.1.0", + "derp==0.1.1", + "tomli>=2.2.1", +] +docs = [ + "sphinx==5.0.0", + "sphinx-rtd-theme==1.0.0", + "sphinxcontrib-apidoc==0.3.0", +] + +[tool.pytest.ini_options] +testpaths = [ + "tests", +] + +[tool.coverage.run] +omit = [ + "gemd/demo/*", +] diff --git a/requirements.txt b/requirements.txt index bc1d0b6f..0af944df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ pint==0.24.4 deprecation==2.1.0 -typing-extensions==4.8.0 -importlib-resources==5.3.0 +typing_extensions>=4.8,<5; python_version<'3.10' diff --git a/setup.py b/setup.py deleted file mode 100644 index cb33c7d3..00000000 --- a/setup.py +++ /dev/null @@ -1,67 +0,0 @@ -from setuptools import setup, find_packages -from pathlib import Path -import re - -packages = find_packages() - -this_directory = Path(__file__).parent.absolute() -version_file = this_directory / 'gemd' / '__version__.py' -version_re = r'''^\s*__version__\s*=\s*(['"])([\w\.]+)\1$''' -if mo := re.search(version_re, version_file.read_text(), re.M): - version = mo.group(2) -else: - raise RuntimeError(f"Unable to find version string in {version_file}") - -setup(name='gemd', - # Update this in gemd/__version__.py - version=version, - python_requires='>=3.9', - url='http://github.com/CitrineInformatics/gemd-python', - description="Python binding for Citrine's GEMD data model", - author='Citrine Informatics', - packages=packages, - package_data={ - 'gemd.demo': [ - 'strehlow_and_cook.pif', - 'strehlow_and_cook_small.pif', - 'toothpick.jpg' - ], - 'gemd.units': [ - 'citrine_en.txt', - 'constants_en.txt', - ], - 'tests.units': ['test_units.txt'] - }, - install_requires=[ - "pint>=0.24.4,<0.25", - "deprecation>=2.1.0,<3", - "typing_extensions>=4.8,<5", - "importlib-resources>=5.3,<7" - ], - extras_require={ - "scripts": [ - "packaging", - "sphinx==5.0.0", - "sphinx-rtd-theme==1.0.0", - "sphinxcontrib-apidoc==0.3.0", - ], - "tests": [ - "pytest>=8.0.0,<9" - ], - "tests.demo": [ - "pandas>=2.0.3,<3" - ], - "tests.entity.bounds": [ - "numpy>=1.24.4,<2; python_version<='3.10'", - "pandas>=2.0.3,<3" - ] - }, - classifiers=[ - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - ], - ) diff --git a/tests/units/test_parser.py b/tests/units/test_parser.py index 3706399d..ffcdeac0 100644 --- a/tests/units/test_parser.py +++ b/tests/units/test_parser.py @@ -1,6 +1,6 @@ from contextlib import contextmanager from deprecation import DeprecatedWarning -from importlib_resources import files +from importlib.resources import files import re from pint import UnitRegistry import pytest diff --git a/tox.ini b/tox.ini index 1cd27ccb..74b1fb87 100644 --- a/tox.ini +++ b/tox.ini @@ -10,9 +10,3 @@ max-doc-length = 119 # D301: backslash is used in making docstrings for sphinx to parse # D401: Imperative mood requirement basically gets in the way ignore = D100,D104,D105,D107,D301,D401 - -[pytest] -testpaths = tests - -[run] -omit = gemd/demo/* From 5486bdbabe0e9c4e11fd61f807009dfb776ab07d Mon Sep 17 00:00:00 2001 From: Ken Kroenlein Date: Wed, 1 Oct 2025 19:44:26 -0600 Subject: [PATCH 2/5] Minor corrections --- gemd/units/impl.py | 2 +- scripts/run_tests.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gemd/units/impl.py b/gemd/units/impl.py index 9e94ce45..0815d9cc 100644 --- a/gemd/units/impl.py +++ b/gemd/units/impl.py @@ -8,7 +8,7 @@ from tempfile import TemporaryDirectory from typing import Union, List, Tuple, Generator, Any try: - from typing import TypeAlias # Python 3.10+ + from typing import TypeAlias # Python 3.10+ except ImportError: # pragma nocover from typing_extensions import TypeAlias # Python 3.9 diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index a44c40b4..6d7371c3 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -36,8 +36,8 @@ then python $REPO_DIR/scripts/validate_version_bump.py || fi flake8 $REPO_DIR/gemd || exit 1; -derp $REPO_DIR $REPO_DIR/gemd/__version__.py || exit 1; +derp $REPO_DIR/gemd $REPO_DIR/gemd/__version__.py || exit 1; pytest $QUIET $EXITFIRST --cov=$REPO_DIR/gemd \ --cov-report term-missing:skip-covered \ - --cov-config=$REPO_DIR/tox.ini --no-cov-on-fail --cov-fail-under=100 \ + --no-cov-on-fail --cov-fail-under=100 \ $REPO_DIR || exit 1; From 7344b9fec9ae699d8d866847d3b37382b7345d6e Mon Sep 17 00:00:00 2001 From: Ken Kroenlein Date: Wed, 1 Oct 2025 19:50:58 -0600 Subject: [PATCH 3/5] Minor corrections, again --- scripts/run_tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index 6d7371c3..b24d9eea 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -36,8 +36,9 @@ then python $REPO_DIR/scripts/validate_version_bump.py || fi flake8 $REPO_DIR/gemd || exit 1; -derp $REPO_DIR/gemd $REPO_DIR/gemd/__version__.py || exit 1; +derp $REPO_DIR/gemd $REPO_DIR/gemd/__version__.py || exit 1; pytest $QUIET $EXITFIRST --cov=$REPO_DIR/gemd \ --cov-report term-missing:skip-covered \ + --cov-config=$REPO_DIR/pyproject.toml \ --no-cov-on-fail --cov-fail-under=100 \ $REPO_DIR || exit 1; From d4f8a210e73c0a902184167d4efd5e0146a2bbc0 Mon Sep 17 00:00:00 2001 From: Ken Kroenlein Date: Wed, 1 Oct 2025 20:02:24 -0600 Subject: [PATCH 4/5] Bump pytest & coverage versions --- pyproject.toml | 4 ++-- test_requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 94a984f9..bea72a39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,8 +51,8 @@ dev = [ "pandas==2.0.3; python_version<'3.10'", "numpy>=2.0.2,<=2.1.0; python_version>='3.10'", "pandas==2.3.0; python_version>='3.10'", - "pytest==8.0.0", - "pytest-cov==4.1.0", + "pytest==8.4.2", + "pytest-cov==7.0.0", "derp==0.1.1", "tomli>=2.2.1", ] diff --git a/test_requirements.txt b/test_requirements.txt index 0460dfc6..c8f0654d 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -4,6 +4,6 @@ numpy==1.24.4; python_version<'3.10' pandas==2.0.3; python_version<'3.10' numpy>=2.0.2,<=2.1.0; python_version>='3.10' pandas==2.3.0; python_version>='3.10' -pytest==8.0.0 -pytest-cov==4.1.0 +pytest==8.4.2 +pytest-cov==7.0.0 derp==0.1.1 From 2d394b7abdb07899c3a544c3481256a9bd9f1b36 Mon Sep 17 00:00:00 2001 From: Ken Kroenlein Date: Wed, 1 Oct 2025 20:07:26 -0600 Subject: [PATCH 5/5] Bump pytest & coverage versions --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 74b1fb87..881efa9c 100644 --- a/tox.ini +++ b/tox.ini @@ -10,3 +10,6 @@ max-doc-length = 119 # D301: backslash is used in making docstrings for sphinx to parse # D401: Imperative mood requirement basically gets in the way ignore = D100,D104,D105,D107,D301,D401 + +[run] +omit = gemd/demo/* \ No newline at end of file