summaryrefslogtreecommitdiff
path: root/pint/registry.py
blob: e978e3698dfd6eaca2050d08857ce93c6c7439c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
"""
    pint.registry
    ~~~~~~~~~~~~~

    Defines the UnitRegistry, a class to contain units and their relations.

    This registry contains all pint capabilities, but you can build your
    customized registry by picking only the features that you actually
    need.

    :copyright: 2022 by Pint Authors, see AUTHORS for more details.
    :license: BSD, see LICENSE for more details.
"""

from __future__ import annotations

from typing import Generic

from . import registry_helpers
from . import facets
from .util import logger, pi_theorem
from .compat import TypeAlias


# To build the Quantity and Unit classes
# we follow the UnitRegistry bases
# but


class Quantity(
    facets.SystemRegistry.Quantity,
    facets.ContextRegistry.Quantity,
    facets.DaskRegistry.Quantity,
    facets.NumpyRegistry.Quantity,
    facets.MeasurementRegistry.Quantity,
    facets.FormattingRegistry.Quantity,
    facets.NonMultiplicativeRegistry.Quantity,
    facets.PlainRegistry.Quantity,
):
    pass


class Unit(
    facets.SystemRegistry.Unit,
    facets.ContextRegistry.Unit,
    facets.DaskRegistry.Unit,
    facets.NumpyRegistry.Unit,
    facets.MeasurementRegistry.Unit,
    facets.FormattingRegistry.Unit,
    facets.NonMultiplicativeRegistry.Unit,
    facets.PlainRegistry.Unit,
):
    pass


class GenericUnitRegistry(
    Generic[facets.QuantityT, facets.UnitT],
    facets.GenericSystemRegistry[facets.QuantityT, facets.UnitT],
    facets.GenericContextRegistry[facets.QuantityT, facets.UnitT],
    facets.GenericDaskRegistry[facets.QuantityT, facets.UnitT],
    facets.GenericNumpyRegistry[facets.QuantityT, facets.UnitT],
    facets.GenericMeasurementRegistry[facets.QuantityT, facets.UnitT],
    facets.GenericFormattingRegistry[facets.QuantityT, facets.UnitT],
    facets.GenericNonMultiplicativeRegistry[facets.QuantityT, facets.UnitT],
    facets.GenericPlainRegistry[facets.QuantityT, facets.UnitT],
):
    pass


class UnitRegistry(GenericUnitRegistry[Quantity, Unit]):
    """The unit registry stores the definitions and relationships between units.

    Parameters
    ----------
    filename :
        path of the units definition file to load or line-iterable object.
        Empty to load the default definition file.
        None to leave the UnitRegistry empty.
    force_ndarray : bool
        convert any input, scalar or not to a numpy.ndarray.
    force_ndarray_like : bool
        convert all inputs other than duck arrays to a numpy.ndarray.
    default_as_delta :
        In the context of a multiplication of units, interpret
        non-multiplicative units as their *delta* counterparts.
    autoconvert_offset_to_baseunit :
        If True converts offset units in quantities are
        converted to their plain units in multiplicative
        context. If False no conversion happens.
    on_redefinition : str
        action to take in case a unit is redefined.
        'warn', 'raise', 'ignore'
    auto_reduce_dimensions :
        If True, reduce dimensionality on appropriate operations.
    preprocessors :
        list of callables which are iteratively ran on any input expression
        or unit string
    fmt_locale :
        locale identifier string, used in `format_babel`. Default to None
    case_sensitive : bool, optional
        Control default case sensitivity of unit parsing. (Default: True)
    cache_folder : str or pathlib.Path or None, optional
        Specify the folder in which cache files are saved and loaded from.
        If None, the cache is disabled. (default)
    """

    Quantity: TypeAlias = Quantity
    Unit: TypeAlias = Unit

    def __init__(
        self,
        filename="",
        force_ndarray: bool = False,
        force_ndarray_like: bool = False,
        default_as_delta: bool = True,
        autoconvert_offset_to_baseunit: bool = False,
        on_redefinition: str = "warn",
        system=None,
        auto_reduce_dimensions=False,
        preprocessors=None,
        fmt_locale=None,
        non_int_type=float,
        case_sensitive: bool = True,
        cache_folder=None,
    ):
        super().__init__(
            filename=filename,
            force_ndarray=force_ndarray,
            force_ndarray_like=force_ndarray_like,
            on_redefinition=on_redefinition,
            default_as_delta=default_as_delta,
            autoconvert_offset_to_baseunit=autoconvert_offset_to_baseunit,
            system=system,
            auto_reduce_dimensions=auto_reduce_dimensions,
            preprocessors=preprocessors,
            fmt_locale=fmt_locale,
            non_int_type=non_int_type,
            case_sensitive=case_sensitive,
            cache_folder=cache_folder,
        )

    def pi_theorem(self, quantities):
        """Builds dimensionless quantities using the Buckingham π theorem

        Parameters
        ----------
        quantities : dict
            mapping between variable name and units

        Returns
        -------
        list
            a list of dimensionless quantities expressed as dicts

        """
        return pi_theorem(quantities, self)

    def setup_matplotlib(self, enable: bool = True) -> None:
        """Set up handlers for matplotlib's unit support.

        Parameters
        ----------
        enable : bool
            whether support should be enabled or disabled (Default value = True)

        """
        # Delays importing matplotlib until it's actually requested
        from .matplotlib import setup_matplotlib_handlers

        setup_matplotlib_handlers(self, enable)

    wraps = registry_helpers.wraps

    check = registry_helpers.check


class LazyRegistry(Generic[facets.QuantityT, facets.UnitT]):
    def __init__(self, args=None, kwargs=None):
        self.__dict__["params"] = args or (), kwargs or {}

    def __init(self):
        args, kwargs = self.__dict__["params"]
        kwargs["on_redefinition"] = "raise"
        self.__class__ = UnitRegistry
        self.__init__(*args, **kwargs)
        self._after_init()

    def __getattr__(self, item):
        if item == "_on_redefinition":
            return "raise"
        self.__init()
        return getattr(self, item)

    def __setattr__(self, key, value):
        if key == "__class__":
            super().__setattr__(key, value)
        else:
            self.__init()
            setattr(self, key, value)

    def __getitem__(self, item):
        self.__init()
        return self[item]

    def __call__(self, *args, **kwargs):
        self.__init()
        return self(*args, **kwargs)


class ApplicationRegistry:
    """A wrapper class used to distribute changes to the application registry."""

    __slots__ = ["_registry"]

    def __init__(self, registry):
        self._registry = registry

    def get(self):
        """Get the wrapped registry"""
        return self._registry

    def set(self, new_registry):
        """Set the new registry

        Parameters
        ----------
        new_registry : ApplicationRegistry or LazyRegistry or UnitRegistry
            The new registry.

        See Also
        --------
        set_application_registry
        """
        if isinstance(new_registry, type(self)):
            new_registry = new_registry.get()

        if not isinstance(new_registry, (LazyRegistry, UnitRegistry)):
            raise TypeError("Expected UnitRegistry; got %s" % type(new_registry))
        logger.debug(
            "Changing app registry from %r to %r.", self._registry, new_registry
        )
        self._registry = new_registry

    def __getattr__(self, name):
        return getattr(self._registry, name)

    def __setattr__(self, name, value):
        if name in self.__slots__:
            super().__setattr__(name, value)
        else:
            setattr(self._registry, name, value)

    def __dir__(self):
        return dir(self._registry)

    def __getitem__(self, item):
        return self._registry[item]

    def __call__(self, *args, **kwargs):
        return self._registry(*args, **kwargs)

    def __contains__(self, item):
        return self._registry.__contains__(item)

    def __iter__(self):
        return iter(self._registry)