From 56186b07728e2e612405cde271fdbf93beefe55a Mon Sep 17 00:00:00 2001 From: Hernan Date: Tue, 25 Oct 2022 17:45:47 -0300 Subject: [PATCH 1/6] Create wheel --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index cfa031f3c..9ce8b48f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,3 +75,4 @@ line_length=88 [zest.releaser] python-file-with-version = version.py +create-wheel = yes From ab6993e07d5d195565685b02cf7c9adacf7ef6bd Mon Sep 17 00:00:00 2001 From: Hernan Date: Tue, 25 Oct 2022 19:33:32 -0300 Subject: [PATCH 2/6] Simplify registry subclassing --- pint/testsuite/test_issues.py | 21 +++++++++++++++++++++ pint/util.py | 15 ++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py index 80e2ebbc3..a07e850fa 100644 --- a/pint/testsuite/test_issues.py +++ b/pint/testsuite/test_issues.py @@ -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) diff --git a/pint/util.py b/pint/util.py index eba747edc..3d0017521 100644 --- a/pint/util.py +++ b/pint/util.py @@ -10,6 +10,7 @@ from __future__ import annotations +import functools import inspect import logging import math @@ -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 @@ -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: From 14406ba8fe847399a1fecf61b6eab8a5d4c4b239 Mon Sep 17 00:00:00 2001 From: Hernan Date: Tue, 25 Oct 2022 21:24:30 -0300 Subject: [PATCH 3/6] Document subclassing --- CHANGES | 2 +- docs/advanced_guides.rst | 1 + docs/user/custom-registry-class.rst | 83 +++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 docs/user/custom-registry-class.rst diff --git a/CHANGES b/CHANGES index 03932a492..04bfc8e1b 100644 --- a/CHANGES +++ b/CHANGES @@ -4,7 +4,7 @@ Pint Changelog 0.21 (unreleased) ----------------- -- Nothing changed yet. +- Simplify registry subclassing and document it. 0.20 (2022-10-25) diff --git a/docs/advanced_guides.rst b/docs/advanced_guides.rst index cc2c495af..580ddb79d 100644 --- a/docs/advanced_guides.rst +++ b/docs/advanced_guides.rst @@ -12,3 +12,4 @@ Advanced Guides user/measurement user/pitheorem user/currencies + user/custom-registry-class diff --git a/docs/user/custom-registry-class.rst b/docs/user/custom-registry-class.rst new file mode 100644 index 000000000..6c029ef9e --- /dev/null +++ b/docs/user/custom-registry-class.rst @@ -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: bas 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. From f7f97ab5b3f8d018dab6e362b4c70c65f7b8ae0b Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Wed, 26 Oct 2022 08:54:30 -0300 Subject: [PATCH 4/6] Typo fix in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mika Pflüger --- docs/user/custom-registry-class.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/custom-registry-class.rst b/docs/user/custom-registry-class.rst index 6c029ef9e..31f3d76fe 100644 --- a/docs/user/custom-registry-class.rst +++ b/docs/user/custom-registry-class.rst @@ -21,7 +21,7 @@ and create your own. - ContextRegistry: the capability to contexts: predefined conversions between incompatible dimensions. - NonMultiplicativeRegistry: adds the capability to handle nonmultiplicative units (offset, logarithmic). -- PlainRegistry: bas implementation for registry, units and quantities. +- PlainRegistry: base implementation for registry, units and quantities. The only required one is `PlainRegistry`, the rest are completely optional. From cc02904f5ef55340c5907f22e70f0578801fe3af Mon Sep 17 00:00:00 2001 From: Hernan Date: Wed, 26 Oct 2022 08:58:16 -0300 Subject: [PATCH 5/6] Test against macos --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e70fd13d7..422fd776f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,7 +92,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.9, "3.10"] + platform: [windows-latest, macos-latest] + 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] @@ -104,7 +105,7 @@ jobs: # numpy: "numpy" # uncertainties: "uncertainties" # extras: "sparse xarray netCDF4 dask[complete] graphviz babel==2.8" - runs-on: windows-latest + runs-on: ${{ matrix.platform }} env: TEST_OPTS: "-rfsxEX -s -k issue1498b" From 5e3c937a4782c1fc55561e90c157eeb68211d203 Mon Sep 17 00:00:00 2001 From: Hernan Date: Wed, 26 Oct 2022 09:10:17 -0300 Subject: [PATCH 6/6] Move macos test to independent section --- .github/workflows/ci.yml | 75 +++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 422fd776f..8e440234f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,20 +92,9 @@ jobs: strategy: fail-fast: false matrix: - platform: [windows-latest, macos-latest] 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: ${{ matrix.platform }} + runs-on: windows-latest env: TEST_OPTS: "-rfsxEX -s -k issue1498b" @@ -160,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