Skip to content

Commit

Permalink
Merge #880
Browse files Browse the repository at this point in the history
880: Global Quantity, Unit, and Measurement r=hgrecco a=crusaderky

This PR addresses several issues I found myself facing when using pint in my own projects.

- I found myself systematically needing to reference UnitRegistry, Quantity, and Unit in sphinx docstrings and typing annotations.
- When prototyping and writing snippet code, I got frequently annoyed by having to typing ``ureg = pint.UnitRegistry()`` every time, even when I was perfectly content with defaults_en.txt (as most novice users will).
-  I found myself needing to access pint._APP_REGISTRY when creating a Quantity in a dask.distributed function.

Changes:
- Define global objects ``pint.Quantity``, ``pint.Unit``, and ``pint.Measurement``. They can be used directly, e.g. ``pint.Quantity(1, "kg")``. They use the global application registry.
- Fix bug where Measurement objects would be accidentally upcast to Quantity upon pickling
- All Exception classes are now accessible from the top-level module (also in intersphinx)
- New function ``pint.get_application_registry``
- Documentation for using ``pint.set_application_registry`` in one's application
- intersphinx users can now reference ``:class:`pint.UnitRegistry` `` instead of the internal implementation module ``:class:`pint.registry.UnitRegistry` ``
- Fixed a wealth of sphinx errors (not all)
- Unit tests for pickling/unpickling custom units through the global registry

Co-authored-by: Guido Imperiale <[email protected]>
Co-authored-by: Guido Imperiale <[email protected]>
Co-authored-by: Unknown <[email protected]>
  • Loading branch information
3 people authored Nov 1, 2019
2 parents b664af6 + 435d193 commit e6ae929
Show file tree
Hide file tree
Showing 27 changed files with 483 additions and 179 deletions.
5 changes: 3 additions & 2 deletions docs/contexts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ raise an error if you do this directly:
>>> q.to('Hz')
Traceback (most recent call last):
...
pint.errors.DimensionalityError: Cannot convert from 'nanometer' ([length]) to 'hertz' (1 / [time])
DimensionalityError: Cannot convert from 'nanometer' ([length]) to 'hertz' (1 / [time])


You probably want to use the relation `frequency = speed_of_light / wavelength`:
Expand Down Expand Up @@ -138,7 +138,8 @@ context and the parameters that you wish to set.
398.496240602


This decorator can be combined with **wraps** or **check** decorators described in `wrapping`_
This decorator can be combined with **wraps** or **check** decorators described in
:doc:`wrapping`.


Defining contexts in a file
Expand Down
7 changes: 5 additions & 2 deletions docs/developers_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Developer reference
Pint
====

.. automodule:: pint
:members:

.. automodule:: pint.babel_names
:members:

Expand Down Expand Up @@ -65,7 +68,7 @@ Pint
.. automodule:: pint.testsuite.helpers
:members:

.. automodule:: pint.testsuite.parametrized
.. automodule:: pint.testsuite.parameterized
:members:

.. automodule:: pint.testsuite.test_babel
Expand All @@ -92,7 +95,7 @@ Pint
.. automodule:: pint.testsuite.test_issues
:members:

.. automodule:: pint.testsuite.test_measurements
.. automodule:: pint.testsuite.test_measurement
:members:

.. automodule:: pint.testsuite.test_numpy
Expand Down
3 changes: 2 additions & 1 deletion docs/getting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ That's all! You can check that Pint is correctly installed by starting up python
.. note:: If you have an old system installation of Python and you don't want to
mess with it, you can try `Anaconda CE`_. It is a free Python distribution by
Continuum Analytics that includes many scientific packages. To install pint
from the conda-forge channel instead of through pip use:
from the conda-forge channel instead of through pip use::

$ conda install -c conda-forge pint

You can check the installation with the following command:
Expand Down
6 changes: 3 additions & 3 deletions docs/nonmult.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ If you want to add a quantity with absolute unit to one with offset unit, like h
>>> Q_(10., ureg.degC) + heating_rate * Q_(30, ureg.min)
Traceback (most recent call last):
...
pint.errors.OffsetUnitCalculusError: Ambiguous operation with offset unit (degC, kelvin).
OffsetUnitCalculusError: Ambiguous operation with offset unit (degC, kelvin).

you have to avoid the ambiguity by either converting the offset unit to the
absolute unit before addition
Expand Down Expand Up @@ -123,7 +123,7 @@ to be explicitly created:
>>> home = 25.4 * ureg.degC
Traceback (most recent call last):
...
pint.errors.OffsetUnitCalculusError: Ambiguous operation with offset unit (degC).
OffsetUnitCalculusError: Ambiguous operation with offset unit (degC).
>>> Q_(25.4, ureg.degC)
<Quantity(25.4, 'degC')>

Expand Down Expand Up @@ -157,7 +157,7 @@ You can change the behaviour at any time:
>>> 1/T
Traceback (most recent call last):
...
pint.errors.OffsetUnitCalculusError: Ambiguous operation with offset unit (degC).
OffsetUnitCalculusError: Ambiguous operation with offset unit (degC).

The parser knows about *delta* units and uses them when a temperature unit
is found in a multiplicative context. For example, here:
Expand Down
4 changes: 2 additions & 2 deletions docs/numpy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ All usual Pint methods can be used with this quantity. For example:
>>> legs1.to('joule')
Traceback (most recent call last):
...
pint.errors.DimensionalityError: Cannot convert from 'meter' ([length]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2)
DimensionalityError: Cannot convert from 'meter' ([length]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2)

NumPy functions are supported by Pint. For example if we define:

Expand Down Expand Up @@ -96,7 +96,7 @@ results in an error:
>>> np.arccos(legs2)
Traceback (most recent call last):
...
pint.errors.DimensionalityError: Cannot convert from 'centimeter' ([length]) to 'dimensionless' (dimensionless)
DimensionalityError: Cannot convert from 'centimeter' ([length]) to 'dimensionless' (dimensionless)


Support
Expand Down
21 changes: 18 additions & 3 deletions docs/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,24 @@ UnitRegistry dependent.
In certain cases, you want a binary representation of the data. Python's standard algorithm
for serialization is called Pickle_. Pint quantities implement the magic `__reduce__`
method and therefore can be *Pickled* and *Unpickled*. However, you have to bear in mind, that
the **DEFAULT_REGISTRY** is used for unpickling and this might be different from the one
that was used during pickling. If you want to have control over the deserialization, the
best way is to create a tuple with the magnitude and the units:
the **application registry** is used for unpickling and this might be different from the one
that was used during pickling.

By default, the application registry is one initialized with :file:`defaults_en.txt`; in
other words, the same as what you get when creating a :class:`pint.UnitRegistry` without
arguments and without adding any definitions afterwards.

If your application is fine just using :file:`defaults_en.txt`, you don't need to worry
further.

If your application needs a single, global registry with custom definitions, you must
make sure that it is registered using :func:`pint.set_application_registry` before
unpickling anything. You may use :func:`pint.get_application_registry` to get the
current instance of the application registry.

Finally, if you need multiple custom registries, it's impossible to correctly unpickle
:class:`pint.Quantity` or :class:`pint.Unit` objects.The best way is to create a tuple
with the magnitude and the units:

.. doctest::

Expand Down
4 changes: 2 additions & 2 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ If you ask Pint to perform an invalid conversion:
>>> speed.to(ureg.joule)
Traceback (most recent call last):
...
pint.errors.DimensionalityError: Cannot convert from 'inch / minute' ([length] / [time]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2)
DimensionalityError: Cannot convert from 'inch / minute' ([length] / [time]) to 'joule' ([length] ** 2 * [mass] / [time] ** 2)

Sometimes, the magnitude of the quantity will be very large or very small.
The method 'to_compact' can adjust the units to make the quantity more
Expand Down Expand Up @@ -170,7 +170,7 @@ If you try to use a unit which is not in the registry:
>>> speed = 23 * ureg.snail_speed
Traceback (most recent call last):
...
pint.errors.UndefinedUnitError: 'snail_speed' is not defined in the unit registry
UndefinedUnitError: 'snail_speed' is not defined in the unit registry

You can add your own units to the registry or build your own list. More info on
that :ref:`defining`
Expand Down
95 changes: 60 additions & 35 deletions pint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@


import pkg_resources

from .context import Context
from .errors import (
DimensionalityError,
OffsetUnitCalculusError,
UndefinedUnitError,
UnitStrippedWarning
)
from .formatting import formatter
from .registry import (UnitRegistry, LazyRegistry)
from .errors import (DimensionalityError, OffsetUnitCalculusError,
UndefinedUnitError, UnitStrippedWarning)
from .measurement import Measurement
from .quantity import Quantity
from .registry import UnitRegistry, LazyRegistry
from .unit import Unit
from .util import pi_theorem, logger

from .context import Context

import sys
try:
Expand All @@ -46,57 +54,74 @@
_APP_REGISTRY = _DEFAULT_REGISTRY


def _build_quantity(value, units):
"""Build Quantity using the Application registry.
Used only for unpickling operations.
"""
from .unit import UnitsContainer

global _APP_REGISTRY

# Prefixed units are defined within the registry
# on parsing (which does not happen here).
# We need to make sure that this happens before using.
if isinstance(units, UnitsContainer):
for name in units.keys():
_APP_REGISTRY.parse_units(name)

return _APP_REGISTRY.Quantity(value, units)

def _unpickle(cls, *args):
"""Rebuild object upon unpickling.
All units must exist in the application registry.
def _build_unit(units):
"""Build Unit using the Application registry.
Used only for unpickling operations.
:param cls:
Quantity, Magnitude, or Unit
"""
from .unit import UnitsContainer

global _APP_REGISTRY

# Prefixed units are defined within the registry
# on parsing (which does not happen here).
# We need to make sure that this happens before using.
if isinstance(units, UnitsContainer):
for name in units.keys():
_APP_REGISTRY.parse_units(name)
for arg in args:
# Prefixed units are defined within the registry
# on parsing (which does not happen here).
# We need to make sure that this happens before using.
if isinstance(arg, UnitsContainer):
for name in arg:
_APP_REGISTRY.parse_units(name)

return _APP_REGISTRY.Unit(units)
return cls(*args)


def set_application_registry(registry):
"""Set the application registry which is used for unpickling operations.
"""Set the application registry, which is used for unpickling operations
and when invoking pint.Quantity or pint.Unit directly.
:param registry: a UnitRegistry instance.
"""
assert isinstance(registry, UnitRegistry)
if not isinstance(registry, (LazyRegistry, UnitRegistry)):
raise TypeError("Expected UnitRegistry; got %s" % type(registry))
global _APP_REGISTRY
logger.debug('Changing app registry from %r to %r.', _APP_REGISTRY, registry)
_APP_REGISTRY = registry


def get_application_registry():
"""Return the application registry. If :func:`set_application_registry` was never
invoked, return a registry built using :file:`defaults_en.txt` embedded in the pint
package.
:param registry: a UnitRegistry instance.
"""
return _APP_REGISTRY


def test():
"""Run all tests.
:return: a :class:`unittest.TestResult` object
"""
from .testsuite import run
return run()


# Enumerate all user-facing objects
# Hint to intersphinx that, when building objects.inv, these objects must be registered
# under the top-level module and not in their original submodules
__all__ = (
'Context',
'Measurement',
'Quantity',
'Unit',
'UnitRegistry',

'DimensionalityError',
'OffsetUnitCalculusError',
'UndefinedUnitError',
'UnitStrippedWarning',

'get_application_registry',
'set_application_registry',
'__version__',
)
5 changes: 3 additions & 2 deletions pint/compat/tokenize.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tokenization help for Python programs.
tokenize(readline) is a generator that breaks a stream of bytes into
Python tokens. It decodes the bytes according to PEP-0263 for
Python tokens. It decodes the bytes according to PEP-0263 for
determining source file encoding.
It accepts a readline-like method which is called repeatedly to get the
Expand Down Expand Up @@ -462,7 +462,8 @@ def tokenize(readline):
must be a callable object which provides the same interface as the
readline() method of built-in file objects. Each call to the function
should return one line of input as bytes. Alternately, readline
can be a callable function terminating with StopIteration:
can be a callable function terminating with StopIteration::
readline = open(myfile, 'rb').__next__ # Example of alternate readline
The generator produces 5-tuples with these members: the token type; the
Expand Down
2 changes: 1 addition & 1 deletion pint/converters.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
pint.converters
~~~~~~~~~
~~~~~~~~~~~~~~~
Functions and classes related to unit conversions.
Expand Down
2 changes: 1 addition & 1 deletion pint/definitions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
pint.definitions
~~~~~~~~~
~~~~~~~~~~~~~~~~
Functions and classes related to unit definitions.
Expand Down
2 changes: 1 addition & 1 deletion pint/errors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
pint.errors
~~~~~~~~~
~~~~~~~~~~~
Functions and classes related to unit definitions and conversions.
Expand Down
2 changes: 1 addition & 1 deletion pint/matplotlib.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
pint.matplotlib
~~~~~~~~~
~~~~~~~~~~~~~~~
Functions and classes related to working with Matplotlib's support
for plotting with units.
Expand Down
Loading

0 comments on commit e6ae929

Please sign in to comment.