diff --git a/CHANGES b/CHANGES index b444cb38e..4c66bc493 100644 --- a/CHANGES +++ b/CHANGES @@ -4,7 +4,7 @@ Pint Changelog 0.17 (unreleased) ----------------- -- Nothing changed yet. +- Fix bugs on `is_compatible_with` with dimensionless types & string parsing errors. (Issue #1190) 0.16.1 (2020-09-22) diff --git a/pint/quantity.py b/pint/quantity.py index 0395a6544..3d20ef7a5 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -569,7 +569,9 @@ def compatible_units(self, *contexts): return self._REGISTRY.get_compatible_units(self._units) def is_compatible_with(self, other, *contexts, **ctx_kwargs): - """ check if the other object is compatible + """Check if other object is compatible. + + Compatibility is decided based on the dimensionality. Parameters ---------- @@ -597,7 +599,8 @@ def is_compatible_with(self, other, *contexts, **ctx_kwargs): if isinstance(other, str): return ( - self.dimensionality == self._REGISTRY.parse_units(other).dimensionality + self.dimensionality + == self._REGISTRY.parse_expression(other).dimensionality ) return self.dimensionless diff --git a/pint/registry.py b/pint/registry.py index a243f4adf..edabea766 100644 --- a/pint/registry.py +++ b/pint/registry.py @@ -898,7 +898,9 @@ def _get_compatible_units(self, input_units, group_or_system): return self._cache.dimensional_equivalents[src_dim] def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs): - """ check if the other object is compatible + """Check if both objects are compatible with each other. + + Compatibility is decided based on the dimensionality. Parameters ---------- @@ -913,7 +915,19 @@ def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs): Returns ------- bool + + Raises + ------ + TypeError + If any objects can't be casted to Quantity to check dimensionality. + UnitUndefinedError + If string parsing does not return any defined unit. """ + if isinstance(obj1, (dict, bool, type(None))) or isinstance( + obj2, (dict, bool, type(None)) + ): + raise TypeError("Type can't be casted to Quantity") + if isinstance(obj1, (self.Quantity, self.Unit)): return obj1.is_compatible_with(obj2, *contexts, **ctx_kwargs) @@ -922,7 +936,10 @@ def is_compatible_with(self, obj1, obj2, *contexts, **ctx_kwargs): obj2, *contexts, **ctx_kwargs ) - return not isinstance(obj2, (self.Quantity, self.Unit)) + if isinstance(obj2, (self.Quantity, self.Unit, str)): + return self.is_compatible_with(obj2, obj1, *contexts, **ctx_kwargs) + else: + return self.Quantity(obj1).is_compatible_with(obj2, *contexts, **ctx_kwargs) def convert(self, value, src, dst, inplace=False): """Convert value from some source to destination units. diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py index dcb3e97a1..6863f1bef 100644 --- a/pint/testsuite/test_unit.py +++ b/pint/testsuite/test_unit.py @@ -3,6 +3,8 @@ import math import re +import pytest + from pint import ( DefinitionSyntaxError, DimensionalityError, @@ -12,6 +14,7 @@ from pint.compat import np from pint.registry import LazyRegistry, UnitRegistry from pint.testsuite import CaseInsensitveQuantityTestCase, QuantityTestCase, helpers +from pint.testsuite.helpers import requires_numpy from pint.testsuite.parameterized import ParameterizedTestCase from pint.util import ParserHelper, UnitsContainer @@ -724,6 +727,55 @@ def test_case_sensitivity(self): ureg.parse_units("j", case_sensitive=False), UnitsContainer(joule=1) ) + def test_is_compatible_with(self): + ureg = self.ureg + + assert ureg.is_compatible_with(ureg.deg, 10.0) + assert ureg.is_compatible_with(10.0, ureg.deg) + assert ureg.is_compatible_with(ureg.km, ureg.m) + + assert ureg.is_compatible_with(999.0, 10.0) + assert ureg.is_compatible_with("80 in", "35000 ft") + assert ureg.is_compatible_with("in", "35000 ft") + assert ureg.is_compatible_with("in", "ft") + assert ureg.is_compatible_with("3500 in", "ft") + assert ureg.is_compatible_with(ureg.m, "35000 ft") + assert ureg.is_compatible_with(ureg.m / ureg.inch, 10.0) + + assert ureg.is_compatible_with(ureg.degC, ureg.K) + assert ureg.is_compatible_with(ureg.degC, ureg.degF) + assert ureg.is_compatible_with(ureg.degC, ureg.delta_degC) + + assert not ureg.is_compatible_with(10.0, ureg.m) + assert not ureg.is_compatible_with(ureg.m, 10.0) + assert not ureg.is_compatible_with(ureg.m, "1 N") + assert not ureg.is_compatible_with("1 N", ureg.m) + + assert not ureg.is_compatible_with(ureg.degC, ureg.m) + assert not ureg.is_compatible_with(ureg.m, ureg.degC) + + assert ureg.is_compatible_with(ureg.hertz, ureg.nm, "spectroscopy") + assert not ureg.is_compatible_with(ureg.hertz, ureg.nm) + + with pytest.raises(TypeError): + ureg.is_compatible_with({}, {}) + ureg.is_compatible_with(None, ureg.deg) + ureg.is_compatible_with(True, ureg.deg) + + with pytest.raises(UndefinedUnitError): + ureg.is_compatible_with("undefined_unit", "m") + ureg.is_compatible_with("m", "undefined_unit") + ureg.is_compatible_with(ureg.undefined_unit, ureg.m) + + @requires_numpy() + def test_is_compatible_with_numpy(self): + ureg = self.ureg + + assert ureg.is_compatible_with([1, 2, 3, 4], 10.0) + assert ureg.is_compatible_with(np.array([1, 2, 3, 4]), ureg.deg) + assert ureg.is_compatible_with(10.0, np.array([1, 2, 3, 4])) + assert not ureg.is_compatible_with(10.0 * ureg.m, np.array([1, 2, 3, 4])) + class TestCaseInsensitiveRegistry(CaseInsensitveQuantityTestCase): def test_case_sensitivity(self): diff --git a/pint/unit.py b/pint/unit.py index eff3c4bca..c768edb59 100644 --- a/pint/unit.py +++ b/pint/unit.py @@ -141,7 +141,9 @@ def compatible_units(self, *contexts): return self._REGISTRY.get_compatible_units(self) def is_compatible_with(self, other, *contexts, **ctx_kwargs): - """ check if the other object is compatible + """Check if the other object is compatible. + + Compatibility is decided based on the dimensionality. Parameters ---------- @@ -169,7 +171,8 @@ def is_compatible_with(self, other, *contexts, **ctx_kwargs): if isinstance(other, str): return ( - self.dimensionality == self._REGISTRY.parse_units(other).dimensionality + self.dimensionality + == self._REGISTRY.parse_expression(other).dimensionality ) return self.dimensionless