From 5bdcd393a14f6fd3305929cea90b41baa2e924b9 Mon Sep 17 00:00:00 2001 From: Filipe Fernandes Date: Thu, 20 Jun 2024 10:31:13 +0200 Subject: [PATCH] Use pint instead of cf_units --- compliance_checker/cfutil.py | 10 ++- compliance_checker/units.py | 120 +++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 compliance_checker/units.py diff --git a/compliance_checker/cfutil.py b/compliance_checker/cfutil.py index 8bcce331..01671bf2 100644 --- a/compliance_checker/cfutil.py +++ b/compliance_checker/cfutil.py @@ -11,6 +11,8 @@ from cf_units import Unit from importlib_resources import files +from compliance_checker.units import units + _UNITLESS_DB = None _SEA_NAMES = None @@ -111,8 +113,12 @@ def is_dimensionless_standard_name(standard_name_table, standard_name): f".//entry[@id='{standard_name}']", ) if found_standard_name is not None: - canonical_units = Unit(found_standard_name.find("canonical_units").text) - return canonical_units.is_dimensionless() + canonical_units = units(found_standard_name.find("canonical_units").text) + print(f"{canonical_units=}") + print(f"{type(canonical_units)=}") + if isinstance(canonical_units, (int, float)): + return True + return canonical_units.dimensionless # if the standard name is not found, assume we need units for the time being else: return False diff --git a/compliance_checker/units.py b/compliance_checker/units.py new file mode 100644 index 00000000..cae8dd9f --- /dev/null +++ b/compliance_checker/units.py @@ -0,0 +1,120 @@ +"""Module to provide unit support via pint approximating UDUNITS/CF.""" + +import functools +import re + +import pint +from pint import ( # noqa: F401 + DimensionalityError, + UndefinedUnitError, + UnitStrippedWarning, +) + +# from `xclim`'s unit support module with permission of the maintainers +try: + + @pint.register_unit_format("cf") + def short_formatter(unit, registry, **options): + """Return a CF-compliant unit string from a `pint` unit. + + Parameters + ---------- + unit : pint.UnitContainer + Input unit. + registry : pint.UnitRegistry + the associated registry + **options + Additional options (may be ignored) + + Returns + ------- + out : str + Units following CF-Convention, using symbols. + """ + import re + + # convert UnitContainer back to Unit + unit = registry.Unit(unit) + # Print units using abbreviations (millimeter -> mm) + s = f"{unit:~D}" + + # Search and replace patterns + pat = r"(?P(?:1 )?/ )?(?P\w+)(?: \*\* (?P\d))?" + + def repl(m): + i, u, p = m.groups() + p = p or (1 if i else "") + neg = "-" if i else "" + + return f"{u}{neg}{p}" + + out, n = re.subn(pat, repl, s) + + # Remove multiplications + out = out.replace(" * ", " ") + # Delta degrees: + out = out.replace("Δ°", "delta_deg") + return out.replace("percent", "%") + +except ImportError: + pass + +# ------ +# Reused with modification from MetPy under the terms of the BSD 3-Clause License. +# Copyright (c) 2015,2017,2019 MetPy Developers. +# 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.compile( + r"(?<=[A-Za-z])(?![A-Za-z])(?=1.6.4 owslib>=0.8.3 packaging pendulum>=1.2.4 +pint pygeoif>=0.6 pyproj>=2.2.1 regex>=2017.07.28