Skip to content

Commit

Permalink
Merge #1633
Browse files Browse the repository at this point in the history
1633: Simplify registry subclassing r=hgrecco a=hgrecco

- [x] Closes #1631
- [x] Executed ``pre-commit run --all-files`` with no errors
- [x] The change is fully covered by automated unit tests
- [x] Documented in docs/ as appropriate
- [x] Added an entry to the CHANGES file


Co-authored-by: Hernan <[email protected]>
Co-authored-by: Hernan Grecco <[email protected]>
  • Loading branch information
bors[bot] and hgrecco authored Oct 26, 2022
2 parents 984923a + 5e3c937 commit cf1a802
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 15 deletions.
74 changes: 63 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.8, 3.9, "3.10"]
python-version: [3.8, 3.9, "3.10", "3.11"]
numpy: [ "numpy>=1.19,<2.0.0" ]
# uncertainties: [null, "uncertainties==3.1.6", "uncertainties>=3.1.6,<4.0.0"]
# extras: [null]
# include:
# - python-version: 3.8 # Minimal versions
# numpy: numpy==1.19.5
# extras: matplotlib==2.2.5
# - python-version: 3.8
# numpy: "numpy"
# uncertainties: "uncertainties"
# extras: "sparse xarray netCDF4 dask[complete] graphviz babel==2.8"
runs-on: windows-latest

env:
Expand Down Expand Up @@ -159,6 +149,68 @@ jobs:
- name: Run tests
run: pytest ${env:TEST_OPTS}

test-macos:
strategy:
fail-fast: false
matrix:
python-version: [3.8, 3.9, "3.10", "3.11"]
numpy: [ "numpy>=1.19,<2.0.0" ]
runs-on: macos-latest

env:
TEST_OPTS: "-rfsxEX -s --cov=pint --cov-config=.coveragerc"

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 100

- name: Get tags
run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Get pip cache dir
id: pip-cache
run: echo "::set-output name=dir::$(pip cache dir)"

- name: Setup caching
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key: pip-${{ matrix.python-version }}
restore-keys: |
pip-${{ matrix.python-version }}
- name: Install numpy
if: ${{ matrix.numpy != null }}
run: pip install "${{matrix.numpy}}"

- name: Install dependencies
run: |
pip install pytest pytest-cov pytest-subtests
pip install .
- name: Run Tests
run: |
pytest $TEST_OPTS
- name: Coverage report
run: coverage report -m

- name: Coveralls Parallel
env:
COVERALLS_FLAG_NAME: ${{ matrix.test-number }}
COVERALLS_PARALLEL: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_SERVICE_NAME: github
run: |
pip install coveralls
coveralls
coveralls:
needs: test-linux
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Pint Changelog
0.21 (unreleased)
-----------------

- Nothing changed yet.
- Simplify registry subclassing and document it.


0.20 (2022-10-25)
Expand Down
1 change: 1 addition & 0 deletions docs/advanced_guides.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Advanced Guides
user/measurement
user/pitheorem
user/currencies
user/custom-registry-class
83 changes: 83 additions & 0 deletions docs/user/custom-registry-class.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.. _custom_registry_class:

Custom registry class
=====================

Pay as you go
-------------

Pint registry functionality is divided into facets. The default
UnitRegistry inherits from all of them, providing a full fledged
and feature rich registry. However, in certain cases you might want
to have a simpler and light registry. Just pick what you need
and create your own.

- FormattingRegistry: adds the capability to format quantities and units into string.
- SystemRegistry: adds the capability to work with system of units.
- GroupRegistry: adds the capability to group units.
- MeasurementRegistry: adds the capability to handle measurements (quantities with uncertainties).
- NumpyRegistry: adds the capability to interoperate with NumPy.
- DaskRegistry: adds the capability to interoperate with Dask.
- ContextRegistry: the capability to contexts: predefined conversions
between incompatible dimensions.
- NonMultiplicativeRegistry: adds the capability to handle nonmultiplicative units (offset, logarithmic).
- PlainRegistry: base implementation for registry, units and quantities.

The only required one is `PlainRegistry`, the rest are completely
optional.

For example:

.. doctest::

>>> import pint
>>> class MyRegistry(pint.facets.NonMultiplicativeRegistry, pint.facets.PlainRegistry):
... pass


Subclassing
-----------

If you want to add the default registry class some specific functionality,
you can subclass it:

.. doctest::

>>> import pint
>>> class MyRegistry(pint.UnitRegistry):
...
... def my_specific_function(self):
... """Do something
... """


If you want to create your own Quantity class, you must tell
your registry about it:

.. doctest::

>>> import pint
>>> class MyQuantity:
...
... # Notice that subclassing pint.Quantity
... # is not necessary.
... # Pint will inspect the Registry class and create
... # a Quantity class that contains all the
... # required parents.
...
... def to_my_desired_format(self):
... """Do something else
... """
>>>
>>> class MyRegistry(pint.UnitRegistry):
...
... _quantity_class = MyQuantity
...
... # The same you can be done with
... # _unit_class
... # _measurement_class


While these examples demonstrate how to add functionality to the default
registry class, you can actually subclass just the PlainRegistry or any
combination of facets.
21 changes: 21 additions & 0 deletions pint/testsuite/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -1014,3 +1014,24 @@ def test_backcompat_speed_velocity(func_registry):
get = func_registry.get_dimensionality
assert get("[velocity]") == UnitsContainer({"[length]": 1, "[time]": -1})
assert get("[speed]") == UnitsContainer({"[length]": 1, "[time]": -1})


def test_issue1631():
import pint

# Test registry subclassing
class MyRegistry(pint.UnitRegistry):
pass

assert MyRegistry.Quantity is pint.UnitRegistry.Quantity
assert MyRegistry.Unit is pint.UnitRegistry.Unit

ureg = MyRegistry()

u = ureg.meter
assert isinstance(u, ureg.Unit)
assert isinstance(u, pint.Unit)

q = 2 * ureg.meter
assert isinstance(q, ureg.Quantity)
assert isinstance(q, pint.Quantity)
15 changes: 12 additions & 3 deletions pint/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from __future__ import annotations

import functools
import inspect
import logging
import math
Expand Down Expand Up @@ -1021,6 +1022,13 @@ def sized(y) -> bool:
return True


@functools.lru_cache(
maxsize=None
) # TODO: replace with cache when Python 3.8 is dropped.
def _build_type(class_name: str, bases):
return type(class_name, bases, dict())


def build_dependent_class(registry_class, class_name: str, attribute_name: str) -> Type:
"""Creates a class specifically for the given registry that
subclass all the classes named by the registry bases in a
Expand All @@ -1036,9 +1044,10 @@ def build_dependent_class(registry_class, class_name: str, attribute_name: str)
for base in inspect.getmro(registry_class)
if attribute_name in base.__dict__
)
bases = dict.fromkeys(bases, None)
newcls = type(class_name, tuple(bases.keys()), dict())
return newcls
bases = tuple(dict.fromkeys(bases, None).keys())
if len(bases) == 1 and bases[0].__name__ == class_name:
return bases[0]
return _build_type(class_name, bases)


def create_class_with_registry(registry, base_class) -> Type:
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ line_length=88

[zest.releaser]
python-file-with-version = version.py
create-wheel = yes

0 comments on commit cf1a802

Please sign in to comment.