Skip to content

Commit fd0df21

Browse files
authored
add a module-global registry (#32)
* use the application registry instead of creating a new one * try to get the unit registry from units or existing quantities * use a module-local registry instead of the application registry * fix get_registry * fix the registry property of DataArray * remove the registry_kwargs test * merge the units in get_registry * don't use the name of the DataArray to set the units * remove a comma * mention that quantify will reject units from multiple registries * document overriding the default registry * rename the default registry to default_registry * load pint into the docs namespace * also load pint_xarray * add a function set up a registry for use with pint_xarray * replace the default registry with the application registry * simplify the setup function * update the docstring * update whats-new.rst * mention that the default registry is the application registry
1 parent b1922e8 commit fd0df21

File tree

5 files changed

+102
-26
lines changed

5 files changed

+102
-26
lines changed

docs/creation.rst

+37-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Attaching units
88
.. ipython:: python
99
:suppress:
1010
11+
import pint
12+
import pint_xarray
1113
import xarray as xr
1214
1315
Usually, when loading data from disk we get a :py:class:`Dataset` or
@@ -40,9 +42,9 @@ We can also override the units of a variable:
4042

4143
In [4]: ds.pint.quantify(b="km")
4244

43-
In [5]: da.pint.quantify({"b": "degree"})
45+
In [5]: da.pint.quantify("degree")
4446

45-
Overriding works, even if there is no ``units`` attribute, so we could use this
47+
Overriding works even if there is no ``units`` attribute, so we could use this
4648
to attach units to a normal :py:class:`Dataset`:
4749

4850
.. ipython::
@@ -53,6 +55,13 @@ to attach units to a normal :py:class:`Dataset`:
5355
Of course, we could use :py:class:`pint.Unit` instances instead of strings to
5456
specify units, too.
5557

58+
.. note::
59+
60+
Unit objects tied to different registries cannot interact with each
61+
other. In order to avoid this, :py:meth:`DataArray.pint.quantify` and
62+
:py:meth:`Dataset.pint.quantify` will make sure only a single registry is
63+
used per ``xarray`` object.
64+
5665
If we wanted to change the units of the data of a :py:class:`DataArray`, we
5766
could do so using the :py:attr:`DataArray.name` attribute:
5867

@@ -76,6 +85,32 @@ have to first swap the dimensions:
7685
...: )
7786
...: da_with_units
7887

88+
By default, :py:meth:`Dataset.pint.quantify` and
89+
:py:meth:`DataArray.pint.quantify` will use the unit registry at
90+
:py:obj:`pint_xarray.unit_registry` (the
91+
:py:func:`application registry <pint.get_application_registry>`). If we want a
92+
different registry, we can either pass it as the ``unit_registry`` parameter:
93+
94+
.. ipython::
95+
96+
In [10]: ureg = pint.UnitRegistry(force_ndarray_like=True)
97+
...: # set up the registry
98+
99+
In [11]: da.pint.quantify("degree", unit_registry=ureg)
100+
101+
or overwrite the default registry:
102+
103+
.. ipython::
104+
105+
In [12]: pint_xarray.unit_registry = ureg
106+
107+
In [13]: da.pint.quantify("degree")
108+
109+
.. note::
110+
111+
To properly work with ``xarray``, the ``force_ndarray_like`` or
112+
``force_ndarray`` options have to be enabled on the custom registry.
113+
79114
Saving with units
80115
-----------------
81116
In order to not lose the units when saving to disk, we first have to call the

docs/whats-new.rst

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ What's new
1313
- fix the :py:attr:`DataArray.pint.units`, :py:attr:`DataArray.pint.magnitude`
1414
and :py:attr:`DataArray.pint.dimensionality` properties and add docstrings for
1515
all three. (:pull:`31`)
16+
- use ``pint``'s application registry as a module-global registry (:pull:`32`)

pint_xarray/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from . import testing # noqa: F401
99
from . import formatting
1010
from .accessors import PintDataArrayAccessor, PintDatasetAccessor # noqa: F401
11+
from .accessors import default_registry as unit_registry # noqa: F401
1112

1213
try:
1314
__version__ = version("pint-xarray")

pint_xarray/accessors.py

+63-19
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@
88

99
from . import conversion
1010

11+
12+
def setup_registry(registry):
13+
"""set up the given registry for use with pint_xarray
14+
15+
Namely, it enables ``force_ndarray_like`` to make sure results are always
16+
duck arrays.
17+
18+
Parameters
19+
----------
20+
registry : pint.UnitRegistry
21+
The registry to modify
22+
"""
23+
if not registry.force_ndarray and not registry.force_ndarray_like:
24+
registry.force_ndarray_like = True
25+
26+
return registry
27+
28+
29+
default_registry = setup_registry(pint.get_application_registry())
30+
1131
# TODO could/should we overwrite xr.open_dataset and xr.open_mfdataset to make
1232
# them apply units upon loading???
1333
# TODO could even override the decode_cf kwarg?
@@ -47,6 +67,16 @@ def zip_mappings(*mappings, fill_value=None):
4767
return zipped
4868

4969

70+
def merge_mappings(first, *mappings):
71+
result = first.copy()
72+
for mapping in mappings:
73+
result.update(
74+
{key: value for key, value in mapping.items() if value is not None}
75+
)
76+
77+
return result
78+
79+
5080
def units_to_str_or_none(mapping):
5181
return {
5282
key: str(value) if isinstance(value, Unit) else value
@@ -72,13 +102,27 @@ def either_dict_or_kwargs(positional, keywords, method_name):
72102
return keywords
73103

74104

75-
def _get_registry(unit_registry, registry_kwargs):
105+
def get_registry(unit_registry, new_units, existing_units):
106+
units = merge_mappings(existing_units, new_units)
107+
registries = {unit._REGISTRY for unit in units.values() if isinstance(unit, Unit)}
108+
76109
if unit_registry is None:
77-
if registry_kwargs is None:
78-
registry_kwargs = {}
79-
registry_kwargs.update(force_ndarray=True)
80-
# TODO should this registry object then be stored somewhere global?
81-
unit_registry = pint.UnitRegistry(**registry_kwargs)
110+
if not registries:
111+
unit_registry = default_registry
112+
elif len(registries) == 1:
113+
(unit_registry,) = registries
114+
registries.add(unit_registry)
115+
116+
if len(registries) > 1 or unit_registry not in registries:
117+
raise ValueError(
118+
"using multiple unit registries in the same object is not supported"
119+
)
120+
121+
if not unit_registry.force_ndarray_like and not unit_registry.force_ndarray:
122+
raise ValueError(
123+
"invalid registry. Please enable 'force_ndarray_like' or 'force_ndarray'."
124+
)
125+
82126
return unit_registry
83127

84128

@@ -110,9 +154,7 @@ class PintDataArrayAccessor:
110154
def __init__(self, da):
111155
self.da = da
112156

113-
def quantify(
114-
self, units=None, unit_registry=None, registry_kwargs=None, **unit_kwargs
115-
):
157+
def quantify(self, units=None, unit_registry=None, **unit_kwargs):
116158
"""
117159
Attaches units to the DataArray.
118160
@@ -145,8 +187,6 @@ def quantify(
145187
unit_registry : pint.UnitRegistry, optional
146188
Unit registry to be used for the units attached to this DataArray.
147189
If not given then a default registry will be created.
148-
registry_kwargs : dict, optional
149-
Keyword arguments to be passed to the unit registry.
150190
**unit_kwargs
151191
Keyword argument form of units.
152192
@@ -187,7 +227,11 @@ def quantify(
187227

188228
units = either_dict_or_kwargs(units, unit_kwargs, ".quantify")
189229

190-
registry = _get_registry(unit_registry, registry_kwargs)
230+
registry = get_registry(
231+
unit_registry,
232+
units,
233+
conversion.extract_units(self.da),
234+
)
191235

192236
unit_attrs = conversion.extract_unit_attributes(self.da)
193237
new_obj = conversion.strip_unit_attributes(self.da)
@@ -254,7 +298,7 @@ def dimensionality(self):
254298
@property
255299
def registry(self):
256300
# TODO is this a bad idea? (see GH issue #1071 in pint)
257-
return self.data._REGISTRY
301+
return getattr(self.da.data, "_REGISTRY", None)
258302

259303
@registry.setter
260304
def registry(self, _):
@@ -385,9 +429,7 @@ class PintDatasetAccessor:
385429
def __init__(self, ds):
386430
self.ds = ds
387431

388-
def quantify(
389-
self, units=None, unit_registry=None, registry_kwargs=None, **unit_kwargs
390-
):
432+
def quantify(self, units=None, unit_registry=None, **unit_kwargs):
391433
"""
392434
Attaches units to each variable in the Dataset.
393435
@@ -420,8 +462,6 @@ def quantify(
420462
Unit registry to be used for the units attached to each
421463
DataArray in this Dataset. If not given then a default
422464
registry will be created.
423-
registry_kwargs : dict, optional
424-
Keyword arguments to be passed to `pint.UnitRegistry`.
425465
**unit_kwargs
426466
Keyword argument form of ``units``.
427467
@@ -458,7 +498,11 @@ def quantify(
458498
b (x) int64 <Quantity([ 5 -2 1], 'decimeter')>
459499
"""
460500
units = either_dict_or_kwargs(units, unit_kwargs, ".quantify")
461-
registry = _get_registry(unit_registry, registry_kwargs)
501+
registry = get_registry(
502+
unit_registry,
503+
units,
504+
conversion.extract_units(self.ds),
505+
)
462506

463507
unit_attrs = conversion.extract_unit_attributes(self.ds)
464508
new_obj = conversion.strip_unit_attributes(self.ds)

pint_xarray/tests/test_accessors.py

-5
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,6 @@ def test_error_on_nonsense_units(self, example_unitless_da):
9494
with pytest.raises(UndefinedUnitError):
9595
da.pint.quantify(units="aecjhbav")
9696

97-
def test_registry_kwargs(self, example_unitless_da):
98-
orig = example_unitless_da
99-
result = orig.pint.quantify(registry_kwargs={"auto_reduce_dimensions": True})
100-
assert result.data._REGISTRY.auto_reduce_dimensions is True
101-
10297

10398
class TestDequantifyDataArray:
10499
def test_strip_units(self, example_quantity_da):

0 commit comments

Comments
 (0)