From 4d852c33777e7bb6665b6fd080d6fbc3903795b2 Mon Sep 17 00:00:00 2001 From: Jon Thielen Date: Mon, 5 Apr 2021 23:36:05 -0500 Subject: [PATCH 01/13] Port MetPy's custom Pint registry to cf-xarray --- cf_xarray/tests/test_units.py | 50 +++++++++++++++++++++++++++++++++++ cf_xarray/units.py | 44 ++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 cf_xarray/tests/test_units.py create mode 100644 cf_xarray/units.py diff --git a/cf_xarray/tests/test_units.py b/cf_xarray/tests/test_units.py new file mode 100644 index 00000000..a54bb12e --- /dev/null +++ b/cf_xarray/tests/test_units.py @@ -0,0 +1,50 @@ +r"""Tests the operation of cf_xarray's ported unit support code. + +Reused with modification from MetPy under the terms of the BSD 3-Clause License. +Copyright (c) 2017 MetPy Developers. +""" + +from ..units import units + + +def test_added_degrees_units(): + """Test that our added degrees units are present in the registry.""" + # Test equivalence of abbreviations/aliases to our defined names + assert str(units("degrees_N").units) == "degrees_north" + assert str(units("degreesN").units) == "degrees_north" + assert str(units("degree_north").units) == "degrees_north" + assert str(units("degree_N").units) == "degrees_north" + assert str(units("degreeN").units) == "degrees_north" + assert str(units("degrees_E").units) == "degrees_east" + assert str(units("degreesE").units) == "degrees_east" + assert str(units("degree_east").units) == "degrees_east" + assert str(units("degree_E").units) == "degrees_east" + assert str(units("degreeE").units) == "degrees_east" + + # Test equivalence of our defined units to base units + assert units("degrees_north") == units("degrees") + assert units("degrees_north").to_base_units().units == units.radian + assert units("degrees_east") == units("degrees") + assert units("degrees_east").to_base_units().units == units.radian + + +def test_gpm_unit(): + """Test that the gpm unit does alias to meters.""" + x = 1 * units("gpm") + assert str(x.units) == "meter" + + +def test_psu_unit(): + """Test that the psu unit are present in the registry.""" + x = 1 * units("psu") + assert str(x.units) == "practical_salinity_units" + + +def test_percent_units(): + """Test that percent sign units are properly parsed and interpreted.""" + assert str(units("%").units) == "percent" + + +def test_udunits_power_syntax(): + """Test that UDUNITS style powers are properly parsed and interpreted.""" + assert units("m2 s-2").units == units.m ** 2 / units.s ** 2 diff --git a/cf_xarray/units.py b/cf_xarray/units.py new file mode 100644 index 00000000..adba2400 --- /dev/null +++ b/cf_xarray/units.py @@ -0,0 +1,44 @@ +r"""Module to provide unit support via pint approximating UDUNITS/CF. + +Reused with modification from MetPy under the terms of the BSD 3-Clause License. +Copyright (c) 2015,2017,2019 MetPy Developers. +""" +import re + +import pint + +UndefinedUnitError = pint.UndefinedUnitError +DimensionalityError = pint.DimensionalityError +UnitStrippedWarning = pint.UnitStrippedWarning + +# Create registry, with preprocessors for UDUNITS-style powers (m2 s-2) and percent signs +units = pint.UnitRegistry( + autoconvert_offset_to_baseunit=True, + preprocessors=[ + functools.partial( + re.sub, + r"(?<=[A-Za-z])(?![A-Za-z])(? Date: Wed, 7 Apr 2021 12:27:49 -0600 Subject: [PATCH 02/13] Add pint to test envs --- ci/environment.yml | 1 + ci/upstream-dev-env.yml | 1 + setup.cfg | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/environment.yml b/ci/environment.yml index 67b99aa9..5c5d951c 100644 --- a/ci/environment.yml +++ b/ci/environment.yml @@ -9,5 +9,6 @@ dependencies: - matplotlib-base - netcdf4 - pandas + - pint - pooch - xarray diff --git a/ci/upstream-dev-env.yml b/ci/upstream-dev-env.yml index 58007acd..69bd2a56 100644 --- a/ci/upstream-dev-env.yml +++ b/ci/upstream-dev-env.yml @@ -12,3 +12,4 @@ dependencies: - pooch - pip: - git+https://github.com/pydata/xarray + - git+https://github.com/hgrecco/pint diff --git a/setup.cfg b/setup.cfg index 2236d52d..1415f9a3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,7 +41,7 @@ skip_gitignore = true force_to_top = true default_section = THIRDPARTY known_first_party = cf_xarray -known_third_party = dask,matplotlib,numpy,pandas,pytest,setuptools,sphinx_autosummary_accessors,xarray +known_third_party = dask,matplotlib,numpy,pandas,pint,pytest,setuptools,sphinx_autosummary_accessors,xarray # Most of the numerical computing stack doesn't have type annotations yet. [mypy-affine.*] From 23a9bec1a11c2b78745b65bb1fd2e5d1585d51a8 Mon Sep 17 00:00:00 2001 From: keewis Date: Wed, 7 Apr 2021 22:02:50 +0200 Subject: [PATCH 03/13] import functools --- cf_xarray/units.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cf_xarray/units.py b/cf_xarray/units.py index adba2400..72666847 100644 --- a/cf_xarray/units.py +++ b/cf_xarray/units.py @@ -3,6 +3,7 @@ Reused with modification from MetPy under the terms of the BSD 3-Clause License. Copyright (c) 2015,2017,2019 MetPy Developers. """ +import functools import re import pint From ad42f4684c21f05b584019bc3528ee1232c2c917 Mon Sep 17 00:00:00 2001 From: dcherian Date: Wed, 7 Apr 2021 15:49:07 -0600 Subject: [PATCH 04/13] Add CI env without optional deps --- .github/workflows/ci.yaml | 23 +++++++++++++++++++++++ cf_xarray/tests/test_units.py | 4 ++++ ci/environment-no-optional-deps.yml | 13 +++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 ci/environment-no-optional-deps.yml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6eb0518b..b3a2636f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,6 +48,29 @@ jobs: name: codecov-umbrella fail_ci_if_error: false + no-optional-deps: + name: no-optional-deps + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + channels: conda-forge + mamba-version: "*" + activate-environment: cf_xarray_test + auto-update-conda: false + python-version: ${{ matrix.python-version }} + - name: Set up conda environment + shell: bash -l {0} + run: | + mamba env update -f ci/environment-no-optional-deps.yml + python -m pip install -e . + conda list + - name: Run Tests + shell: bash -l {0} + run: | + pytest -n 2 + upstream-dev: name: upstream-dev runs-on: ubuntu-latest diff --git a/cf_xarray/tests/test_units.py b/cf_xarray/tests/test_units.py index a54bb12e..885c8441 100644 --- a/cf_xarray/tests/test_units.py +++ b/cf_xarray/tests/test_units.py @@ -4,8 +4,12 @@ Copyright (c) 2017 MetPy Developers. """ +import pytest + from ..units import units +pytest.importskip("pint") + def test_added_degrees_units(): """Test that our added degrees units are present in the registry.""" diff --git a/ci/environment-no-optional-deps.yml b/ci/environment-no-optional-deps.yml new file mode 100644 index 00000000..67b99aa9 --- /dev/null +++ b/ci/environment-no-optional-deps.yml @@ -0,0 +1,13 @@ +name: cf_xarray_test +channels: + - conda-forge +dependencies: + - pytest-cov + - pytest + - pytest-xdist + - dask + - matplotlib-base + - netcdf4 + - pandas + - pooch + - xarray From e9699b9a412c555e31a8a1be5089fd1614be9777 Mon Sep 17 00:00:00 2001 From: dcherian Date: Wed, 7 Apr 2021 15:56:58 -0600 Subject: [PATCH 05/13] typo --- cf_xarray/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cf_xarray/tests/test_units.py b/cf_xarray/tests/test_units.py index 885c8441..9dccc85a 100644 --- a/cf_xarray/tests/test_units.py +++ b/cf_xarray/tests/test_units.py @@ -8,7 +8,7 @@ from ..units import units -pytest.importskip("pint") +pytest.importorskip("pint") def test_added_degrees_units(): From d4a7bae57ad5398d99b597ad94efe609e11991ab Mon Sep 17 00:00:00 2001 From: dcherian Date: Wed, 7 Apr 2021 16:14:13 -0600 Subject: [PATCH 06/13] fix order of imports --- cf_xarray/tests/test_units.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cf_xarray/tests/test_units.py b/cf_xarray/tests/test_units.py index 9dccc85a..12e95adf 100644 --- a/cf_xarray/tests/test_units.py +++ b/cf_xarray/tests/test_units.py @@ -6,10 +6,10 @@ import pytest -from ..units import units - pytest.importorskip("pint") +from ..units import units + def test_added_degrees_units(): """Test that our added degrees units are present in the registry.""" From 0c773e1bf837bcd6147db241e19227310e3650ac Mon Sep 17 00:00:00 2001 From: Jon Thielen Date: Thu, 8 Apr 2021 13:17:21 -0500 Subject: [PATCH 07/13] Updates based on review feedback --- cf_xarray/tests/test_units.py | 2 +- cf_xarray/units.py | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cf_xarray/tests/test_units.py b/cf_xarray/tests/test_units.py index 12e95adf..a0934316 100644 --- a/cf_xarray/tests/test_units.py +++ b/cf_xarray/tests/test_units.py @@ -41,7 +41,7 @@ def test_gpm_unit(): def test_psu_unit(): """Test that the psu unit are present in the registry.""" x = 1 * units("psu") - assert str(x.units) == "practical_salinity_units" + assert str(x.units) == "practical_salinity_unit" def test_percent_units(): diff --git a/cf_xarray/units.py b/cf_xarray/units.py index 72666847..9a693d07 100644 --- a/cf_xarray/units.py +++ b/cf_xarray/units.py @@ -5,12 +5,11 @@ """ import functools import re +import warnings import pint +from pint import DimensionalityError, UndefinedUnitError, UnitStrippedWarning -UndefinedUnitError = pint.UndefinedUnitError -DimensionalityError = pint.DimensionalityError -UnitStrippedWarning = pint.UnitStrippedWarning # Create registry, with preprocessors for UDUNITS-style powers (m2 s-2) and percent signs units = pint.UnitRegistry( @@ -23,6 +22,7 @@ ), lambda string: string.replace("%", "percent"), ], + force_ndarray_like=True ) units.define( @@ -37,9 +37,18 @@ "degrees_east = degree = degrees_E = degreesE = degree_east = degree_E = degreeE" ) units.define("@alias meter = gpm") -units.define("practical_salinity_units = [] = psu") +units.define("practical_salinity_unit = [] = psu") # Enable pint's built-in matplotlib support -units.setup_matplotlib() +try: + units.setup_matplotlib() +except ImportError: + warnings.warn( + "Import(s) unavailable to set up matplotlib support...skipping this portion " + "of the setup." + ) + +# Set as application registry +pint.set_application_registry(units) del pint From 3c9225c3d5b2e23e0d70d746815c6368953addcf Mon Sep 17 00:00:00 2001 From: Jon Thielen Date: Thu, 8 Apr 2021 13:40:14 -0500 Subject: [PATCH 08/13] black/isort run --- cf_xarray/units.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cf_xarray/units.py b/cf_xarray/units.py index 9a693d07..6fbfa4a6 100644 --- a/cf_xarray/units.py +++ b/cf_xarray/units.py @@ -10,7 +10,6 @@ import pint from pint import DimensionalityError, UndefinedUnitError, UnitStrippedWarning - # Create registry, with preprocessors for UDUNITS-style powers (m2 s-2) and percent signs units = pint.UnitRegistry( autoconvert_offset_to_baseunit=True, @@ -22,7 +21,7 @@ ), lambda string: string.replace("%", "percent"), ], - force_ndarray_like=True + force_ndarray_like=True, ) units.define( From d11248351f2140a4a68efd8471bf0b17bd2fe2db Mon Sep 17 00:00:00 2001 From: Jon Thielen Date: Fri, 16 Apr 2021 12:56:47 -0500 Subject: [PATCH 09/13] Update cf_xarray/units.py with suggestion Co-authored-by: keewis --- cf_xarray/units.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cf_xarray/units.py b/cf_xarray/units.py index 6fbfa4a6..57c4903d 100644 --- a/cf_xarray/units.py +++ b/cf_xarray/units.py @@ -15,8 +15,7 @@ autoconvert_offset_to_baseunit=True, preprocessors=[ functools.partial( - re.sub, - r"(?<=[A-Za-z])(?![A-Za-z])(? Date: Wed, 28 Apr 2021 08:14:09 -0600 Subject: [PATCH 10/13] Update cf_xarray/tests/test_units.py Co-authored-by: keewis --- cf_xarray/tests/test_units.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cf_xarray/tests/test_units.py b/cf_xarray/tests/test_units.py index a0934316..17398d4b 100644 --- a/cf_xarray/tests/test_units.py +++ b/cf_xarray/tests/test_units.py @@ -49,6 +49,12 @@ def test_percent_units(): assert str(units("%").units) == "percent" +@pytest.mark.xfail(reason="not supported by pint, yet: hgrecco/pint#1295") def test_udunits_power_syntax(): """Test that UDUNITS style powers are properly parsed and interpreted.""" assert units("m2 s-2").units == units.m ** 2 / units.s ** 2 + + +def test_udunits_power_syntax_parse_units(): + """Test that UDUNITS style powers are properly parsed and interpreted.""" + assert units.parse_units("m2 s-2") == units.m ** 2 / units.s ** 2 From 82d9a520fdf5d2f93292c3c00758588d4ca0e401 Mon Sep 17 00:00:00 2001 From: Keewis Date: Wed, 28 Apr 2021 16:29:29 +0200 Subject: [PATCH 11/13] linting --- cf_xarray/units.py | 4 +++- setup.cfg | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cf_xarray/units.py b/cf_xarray/units.py index 57c4903d..9444490f 100644 --- a/cf_xarray/units.py +++ b/cf_xarray/units.py @@ -15,7 +15,9 @@ autoconvert_offset_to_baseunit=True, preprocessors=[ functools.partial( - re.compile(r"(?<=[A-Za-z])(?![A-Za-z])(? Date: Wed, 28 Apr 2021 16:30:10 +0200 Subject: [PATCH 12/13] ignore flake8 unused import errors --- cf_xarray/units.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cf_xarray/units.py b/cf_xarray/units.py index 9444490f..4aad7a4f 100644 --- a/cf_xarray/units.py +++ b/cf_xarray/units.py @@ -8,7 +8,11 @@ import warnings import pint -from pint import DimensionalityError, UndefinedUnitError, UnitStrippedWarning +from pint import ( # noqa: F401 + DimensionalityError, + UndefinedUnitError, + UnitStrippedWarning, +) # Create registry, with preprocessors for UDUNITS-style powers (m2 s-2) and percent signs units = pint.UnitRegistry( From af01b74afd84f1ef7b625fbc8c5a454a309b91f1 Mon Sep 17 00:00:00 2001 From: dcherian Date: Tue, 18 May 2021 11:53:24 -0600 Subject: [PATCH 13/13] add whats-new --- doc/whats-new.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index af1a88be..66a080d8 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -6,6 +6,8 @@ What's New v0.5.2 (unreleased) =================== +- Begin adding support for units with a unit registry for pint arrays. :pr:`197`. + By `Jon Thielen`_ and `Justus Magin`_. - Added :py:attr:`DataArray.cf.formula_terms` and :py:attr:`Dataset.cf.formula_terms`. By `Deepak Cherian`_. - Added :py:attr:`Dataset.cf.bounds` to return a dictionary mapping valid keys to the variable names of their bounds. By `Mattia Almansi`_. @@ -100,6 +102,8 @@ v0.1.3 - Support expanding key to multiple dimension names. .. _`Mattia Almansi`: https://github.com/malmans2 +.. _`Justus Magin`: https://github.com/keewis +.. _`Jon Thielen`: https://github.com/jthielen .. _`Anderson Banihirwe`: https://github.com/andersy005 .. _`Pascal Bourgault`: https://github.com/aulemahal .. _`Deepak Cherian`: https://github.com/dcherian