|
4 | 4 | import re |
5 | 5 |
|
6 | 6 | import pint |
7 | | -from pint import ( # noqa: F401 |
8 | | - DimensionalityError, |
9 | | - UndefinedUnitError, |
10 | | - UnitStrippedWarning, |
11 | | -) |
| 7 | +from packaging.version import Version |
12 | 8 |
|
13 | 9 | from .utils import emit_user_level_warning |
14 | 10 |
|
15 | | -# from `xclim`'s unit support module with permission of the maintainers |
16 | | -try: |
17 | 11 |
|
18 | | - @pint.register_unit_format("cf") |
19 | | - def short_formatter(unit, registry, **options): |
20 | | - """Return a CF-compliant unit string from a `pint` unit. |
21 | | -
|
22 | | - Parameters |
23 | | - ---------- |
24 | | - unit : pint.UnitContainer |
25 | | - Input unit. |
26 | | - registry : pint.UnitRegistry |
27 | | - the associated registry |
28 | | - **options |
29 | | - Additional options (may be ignored) |
30 | | -
|
31 | | - Returns |
32 | | - ------- |
33 | | - out : str |
34 | | - Units following CF-Convention, using symbols. |
35 | | - """ |
36 | | - import re |
37 | | - |
38 | | - # convert UnitContainer back to Unit |
39 | | - unit = registry.Unit(unit) |
40 | | - # Print units using abbreviations (millimeter -> mm) |
41 | | - s = f"{unit:~D}" |
42 | | - |
43 | | - # Search and replace patterns |
44 | | - pat = r"(?P<inverse>(?:1 )?/ )?(?P<unit>\w+)(?: \*\* (?P<pow>\d))?" |
45 | | - |
46 | | - def repl(m): |
47 | | - i, u, p = m.groups() |
48 | | - p = p or (1 if i else "") |
49 | | - neg = "-" if i else "" |
50 | | - |
51 | | - return f"{u}{neg}{p}" |
52 | | - |
53 | | - out, n = re.subn(pat, repl, s) |
54 | | - |
55 | | - # Remove multiplications |
56 | | - out = out.replace(" * ", " ") |
57 | | - # Delta degrees: |
58 | | - out = out.replace("Δ°", "delta_deg") |
59 | | - return out.replace("percent", "%") |
| 12 | +@pint.register_unit_format("cf") |
| 13 | +def short_formatter(unit, registry, **options): |
| 14 | + """Return a CF-compliant unit string from a `pint` unit. |
| 15 | +
|
| 16 | + Parameters |
| 17 | + ---------- |
| 18 | + unit : pint.UnitContainer |
| 19 | + Input unit. |
| 20 | + registry : pint.UnitRegistry |
| 21 | + The associated registry |
| 22 | + **options |
| 23 | + Additional options (may be ignored) |
| 24 | +
|
| 25 | + Returns |
| 26 | + ------- |
| 27 | + out : str |
| 28 | + Units following CF-Convention, using symbols. |
| 29 | + """ |
| 30 | + # pint 0.24.1 gives {"dimensionless": 1} for non-shortened dimensionless units |
| 31 | + # CF uses "1" to denote fractions and dimensionless quantities |
| 32 | + if unit == {"dimensionless": 1} or not unit: |
| 33 | + return "1" |
| 34 | + |
| 35 | + # If u is a name, get its symbol (same as pint's "~" pre-formatter) |
| 36 | + # otherwise, assume a symbol (pint should have already raised on invalid units before this) |
| 37 | + unit = pint.util.UnitsContainer( |
| 38 | + { |
| 39 | + registry._get_symbol(u) if u in registry._units else u: exp |
| 40 | + for u, exp in unit.items() |
| 41 | + } |
| 42 | + ) |
| 43 | + |
| 44 | + # Change in formatter signature in pint 0.24 |
| 45 | + if Version(pint.__version__) < Version("0.24"): |
| 46 | + args = (unit.items(),) |
| 47 | + else: |
| 48 | + # Numerators splitted from denominators |
| 49 | + args = ( |
| 50 | + ((u, e) for u, e in unit.items() if e >= 0), |
| 51 | + ((u, e) for u, e in unit.items() if e < 0), |
| 52 | + ) |
| 53 | + |
| 54 | + out = pint.formatter(*args, as_ratio=False, product_fmt=" ", power_fmt="{}{}") |
| 55 | + # To avoid potentiel unicode problems in netCDF. In both cases, this unit is not recognized by udunits |
| 56 | + return out.replace("Δ°", "delta_deg") |
60 | 57 |
|
61 | | -except ImportError: |
62 | | - pass |
63 | 58 |
|
64 | 59 | # ------ |
65 | 60 | # Reused with modification from MetPy under the terms of the BSD 3-Clause License. |
|
0 commit comments