Skip to content

add a module-global registry #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c68ce57
use the application registry instead of creating a new one
keewis Sep 2, 2020
322400a
try to get the unit registry from units or existing quantities
keewis Sep 2, 2020
b21728f
use a module-local registry instead of the application registry
keewis Sep 2, 2020
4af2849
fix get_registry
keewis Sep 2, 2020
9768374
fix the registry property of DataArray
keewis Sep 2, 2020
aa26ce2
remove the registry_kwargs test
keewis Sep 3, 2020
e48b95d
merge the units in get_registry
keewis Sep 4, 2020
6efdece
don't use the name of the DataArray to set the units
keewis Sep 4, 2020
f890a36
remove a comma
keewis Sep 4, 2020
02b8cc1
mention that quantify will reject units from multiple registries
keewis Sep 4, 2020
b85d5c1
document overriding the default registry
keewis Sep 4, 2020
10576a7
Merge branch 'master' into global-registry
keewis Sep 4, 2020
1287eda
rename the default registry to default_registry
keewis Sep 4, 2020
5c4152b
load pint into the docs namespace
keewis Sep 4, 2020
8824f35
also load pint_xarray
keewis Sep 4, 2020
c22d7b2
Merge branch 'master' into global-registry
keewis Oct 22, 2020
f9700a9
add a function set up a registry for use with pint_xarray
keewis Oct 22, 2020
b675ee3
replace the default registry with the application registry
keewis Oct 22, 2020
fffa6c7
simplify the setup function
keewis Oct 22, 2020
ed8de4e
update the docstring
keewis Oct 22, 2020
20a3e3e
update whats-new.rst
keewis Oct 22, 2020
dffd9d2
mention that the default registry is the application registry
keewis Oct 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions docs/creation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Attaching units
.. ipython:: python
:suppress:

import pint
import pint_xarray
import xarray as xr

Usually, when loading data from disk we get a :py:class:`Dataset` or
Expand Down Expand Up @@ -40,9 +42,9 @@ We can also override the units of a variable:

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

In [5]: da.pint.quantify({"b": "degree"})
In [5]: da.pint.quantify("degree")

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

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

.. note::

Unit objects tied to different registries cannot interact with each
other. In order to avoid this, :py:meth:`DataArray.pint.quantify` and
:py:meth:`Dataset.pint.quantify` will make sure only a single registry is
used per ``xarray`` object.

If we wanted to change the units of the data of a :py:class:`DataArray`, we
could do so using the :py:attr:`DataArray.name` attribute:

Expand All @@ -76,6 +85,32 @@ have to first swap the dimensions:
...: )
...: da_with_units

By default, :py:meth:`Dataset.pint.quantify` and
:py:meth:`DataArray.pint.quantify` will use the unit registry at
:py:obj:`pint_xarray.unit_registry` (the
:py:func:`application registry <pint.get_application_registry>`). If we want a
different registry, we can either pass it as the ``unit_registry`` parameter:

.. ipython::

In [10]: ureg = pint.UnitRegistry(force_ndarray_like=True)
...: # set up the registry

In [11]: da.pint.quantify("degree", unit_registry=ureg)

or overwrite the default registry:

.. ipython::

In [12]: pint_xarray.unit_registry = ureg

In [13]: da.pint.quantify("degree")

.. note::

To properly work with ``xarray``, the ``force_ndarray_like`` or
``force_ndarray`` options have to be enabled on the custom registry.

Saving with units
-----------------
In order to not lose the units when saving to disk, we first have to call the
Expand Down
1 change: 1 addition & 0 deletions docs/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ What's new
- fix the :py:attr:`DataArray.pint.units`, :py:attr:`DataArray.pint.magnitude`
and :py:attr:`DataArray.pint.dimensionality` properties and add docstrings for
all three. (:pull:`31`)
- use ``pint``'s application registry as a module-global registry (:pull:`32`)
1 change: 1 addition & 0 deletions pint_xarray/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from . import testing # noqa: F401
from . import formatting
from .accessors import PintDataArrayAccessor, PintDatasetAccessor # noqa: F401
from .accessors import default_registry as unit_registry # noqa: F401

try:
__version__ = version("pint-xarray")
Expand Down
82 changes: 63 additions & 19 deletions pint_xarray/accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@

from . import conversion


def setup_registry(registry):
"""set up the given registry for use with pint_xarray

Namely, it enables ``force_ndarray_like`` to make sure results are always
duck arrays.

Parameters
----------
registry : pint.UnitRegistry
The registry to modify
"""
if not registry.force_ndarray and not registry.force_ndarray_like:
registry.force_ndarray_like = True

return registry


default_registry = setup_registry(pint.get_application_registry())

# TODO could/should we overwrite xr.open_dataset and xr.open_mfdataset to make
# them apply units upon loading???
# TODO could even override the decode_cf kwarg?
Expand Down Expand Up @@ -47,6 +67,16 @@ def zip_mappings(*mappings, fill_value=None):
return zipped


def merge_mappings(first, *mappings):
result = first.copy()
for mapping in mappings:
result.update(
{key: value for key, value in mapping.items() if value is not None}
)

return result


def units_to_str_or_none(mapping):
return {
key: str(value) if isinstance(value, Unit) else value
Expand All @@ -72,13 +102,27 @@ def either_dict_or_kwargs(positional, keywords, method_name):
return keywords


def _get_registry(unit_registry, registry_kwargs):
def get_registry(unit_registry, new_units, existing_units):
units = merge_mappings(existing_units, new_units)
registries = {unit._REGISTRY for unit in units.values() if isinstance(unit, Unit)}

if unit_registry is None:
if registry_kwargs is None:
registry_kwargs = {}
registry_kwargs.update(force_ndarray=True)
# TODO should this registry object then be stored somewhere global?
unit_registry = pint.UnitRegistry(**registry_kwargs)
if not registries:
unit_registry = default_registry
elif len(registries) == 1:
(unit_registry,) = registries
registries.add(unit_registry)

if len(registries) > 1 or unit_registry not in registries:
raise ValueError(
"using multiple unit registries in the same object is not supported"
)

if not unit_registry.force_ndarray_like and not unit_registry.force_ndarray:
raise ValueError(
"invalid registry. Please enable 'force_ndarray_like' or 'force_ndarray'."
)

return unit_registry


Expand Down Expand Up @@ -110,9 +154,7 @@ class PintDataArrayAccessor:
def __init__(self, da):
self.da = da

def quantify(
self, units=None, unit_registry=None, registry_kwargs=None, **unit_kwargs
):
def quantify(self, units=None, unit_registry=None, **unit_kwargs):
"""
Attaches units to the DataArray.

Expand Down Expand Up @@ -145,8 +187,6 @@ def quantify(
unit_registry : pint.UnitRegistry, optional
Unit registry to be used for the units attached to this DataArray.
If not given then a default registry will be created.
registry_kwargs : dict, optional
Keyword arguments to be passed to the unit registry.
**unit_kwargs
Keyword argument form of units.

Expand Down Expand Up @@ -187,7 +227,11 @@ def quantify(

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

registry = _get_registry(unit_registry, registry_kwargs)
registry = get_registry(
unit_registry,
units,
conversion.extract_units(self.da),
)

unit_attrs = conversion.extract_unit_attributes(self.da)
new_obj = conversion.strip_unit_attributes(self.da)
Expand Down Expand Up @@ -254,7 +298,7 @@ def dimensionality(self):
@property
def registry(self):
# TODO is this a bad idea? (see GH issue #1071 in pint)
return self.data._REGISTRY
return getattr(self.da.data, "_REGISTRY", None)

@registry.setter
def registry(self, _):
Expand Down Expand Up @@ -385,9 +429,7 @@ class PintDatasetAccessor:
def __init__(self, ds):
self.ds = ds

def quantify(
self, units=None, unit_registry=None, registry_kwargs=None, **unit_kwargs
):
def quantify(self, units=None, unit_registry=None, **unit_kwargs):
"""
Attaches units to each variable in the Dataset.

Expand Down Expand Up @@ -420,8 +462,6 @@ def quantify(
Unit registry to be used for the units attached to each
DataArray in this Dataset. If not given then a default
registry will be created.
registry_kwargs : dict, optional
Keyword arguments to be passed to `pint.UnitRegistry`.
**unit_kwargs
Keyword argument form of ``units``.

Expand Down Expand Up @@ -458,7 +498,11 @@ def quantify(
b (x) int64 <Quantity([ 5 -2 1], 'decimeter')>
"""
units = either_dict_or_kwargs(units, unit_kwargs, ".quantify")
registry = _get_registry(unit_registry, registry_kwargs)
registry = get_registry(
unit_registry,
units,
conversion.extract_units(self.ds),
)

unit_attrs = conversion.extract_unit_attributes(self.ds)
new_obj = conversion.strip_unit_attributes(self.ds)
Expand Down
5 changes: 0 additions & 5 deletions pint_xarray/tests/test_accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ def test_error_on_nonsense_units(self, example_unitless_da):
with pytest.raises(UndefinedUnitError):
da.pint.quantify(units="aecjhbav")

def test_registry_kwargs(self, example_unitless_da):
orig = example_unitless_da
result = orig.pint.quantify(registry_kwargs={"auto_reduce_dimensions": True})
assert result.data._REGISTRY.auto_reduce_dimensions is True


class TestDequantifyDataArray:
def test_strip_units(self, example_quantity_da):
Expand Down