Skip to content

Commit

Permalink
Add case_sensitive regsitry setting and modify case_sensitive kwarg t…
Browse files Browse the repository at this point in the history
…o default to None to use regsitry setting
  • Loading branch information
jthielen committed Aug 16, 2020
1 parent 3d99581 commit 1161c8b
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Pint Changelog
0.15 (unreleased)
-----------------

- Add `case_sensitive` option to registry for case (in)sensitive handling when parsing
units (Issue #1145)
- Implement Dask collection interface to support Pint Quantity wrapped Dask arrays.
- Started automatically testing examples in the documentation
- Fixed right operand power for dimensionless Quantity to reflect numpy behavior. (Issue #1136)
Expand Down
53 changes: 35 additions & 18 deletions pint/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ class BaseRegistry(metaclass=RegistryMeta):
locale identifier string, used in `format_babel`
non_int_type : type
numerical type used for non integer values. (Default: float)
case_sensitive : bool, optional
Control default case sensitivity of unit parsing. (Default: True)
"""

Expand All @@ -187,6 +189,7 @@ def __init__(
preprocessors=None,
fmt_locale=None,
non_int_type=float,
case_sensitive=True,
):
self._register_parsers()
self._init_dynamic_classes()
Expand All @@ -208,6 +211,9 @@ def __init__(
#: Numerical type used for non integer values.
self.non_int_type = non_int_type

#: Default unit case sensitivity
self.case_sensitive = case_sensitive

#: Map between name (string) and value (string) of defaults stored in the
#: definitions file.
self._defaults = {}
Expand Down Expand Up @@ -619,7 +625,7 @@ def _build_cache(self):
except Exception as exc:
logger.warning(f"Could not resolve {unit_name}: {exc!r}")

def get_name(self, name_or_alias, case_sensitive=True):
def get_name(self, name_or_alias, case_sensitive=None):
"""Return the canonical name of a unit.
"""

Expand All @@ -645,7 +651,7 @@ def get_name(self, name_or_alias, case_sensitive=True):

if prefix:
name = prefix + unit_name
symbol = self.get_symbol(name)
symbol = self.get_symbol(name, case_sensitive)
prefix_def = self._prefixes[prefix]
self._units[name] = UnitDefinition(
name,
Expand All @@ -658,10 +664,10 @@ def get_name(self, name_or_alias, case_sensitive=True):

return unit_name

def get_symbol(self, name_or_alias):
def get_symbol(self, name_or_alias, case_sensitive=None):
"""Return the preferred alias for a unit.
"""
candidates = self.parse_unit_name(name_or_alias)
candidates = self.parse_unit_name(name_or_alias, case_sensitive)
if not candidates:
raise UndefinedUnitError(name_or_alias)
elif len(candidates) == 1:
Expand Down Expand Up @@ -994,7 +1000,7 @@ def _convert(self, value, src, dst, inplace=False, check_dimensionality=True):

return value

def parse_unit_name(self, unit_name, case_sensitive=True):
def parse_unit_name(self, unit_name, case_sensitive=None):
"""Parse a unit to identify prefix, unit name and suffix
by walking the list of prefix and suffix.
In case of equivalent combinations (e.g. ('kilo', 'gram', '') and
Expand All @@ -1004,14 +1010,18 @@ def parse_unit_name(self, unit_name, case_sensitive=True):
----------
unit_name :
case_sensitive :
(Default value = True)
case_sensitive : bool or None
Control if unit lookup is case sensitive. Defaults to None, which uses the
registry's case_sensitive setting
Returns
-------
tuple of tuples (str, str, str)
all non-equivalent combinations of (prefix, unit name, suffix)
"""
case_sensitive = (
self.case_sensitive if case_sensitive is None else case_sensitive
)
return self._dedup_candidates(
self._parse_unit_name(unit_name, case_sensitive=case_sensitive)
)
Expand Down Expand Up @@ -1062,7 +1072,7 @@ def _dedup_candidates(candidates):
candidates.pop(("", cp + cu, ""), None)
return tuple(candidates)

def parse_units(self, input_string, as_delta=None):
def parse_units(self, input_string, as_delta=None, case_sensitive=None):
"""Parse a units expression and returns a UnitContainer with
the canonical names.
Expand All @@ -1074,17 +1084,20 @@ def parse_units(self, input_string, as_delta=None):
as_delta : bool or None
if the expression has multiple units, the parser will
interpret non multiplicative units as their `delta_` counterparts. (Default value = None)
case_sensitive : bool or None
Control if unit parsing is case sensitive. Defaults to None, which uses the
registry's setting.
Returns
-------
"""
for p in self.preprocessors:
input_string = p(input_string)
units = self._parse_units(input_string, as_delta)
units = self._parse_units(input_string, as_delta, case_sensitive)
return self.Unit(units)

def _parse_units(self, input_string, as_delta=True):
def _parse_units(self, input_string, as_delta=True, case_sensitive=None):
"""Parse a units expression and returns a UnitContainer with
the canonical names.
"""
Expand All @@ -1109,7 +1122,7 @@ def _parse_units(self, input_string, as_delta=True):
ret = {}
many = len(units) > 1
for name in units:
cname = self.get_name(name)
cname = self.get_name(name, case_sensitive=case_sensitive)
value = units[name]
if not cname:
continue
Expand All @@ -1126,7 +1139,7 @@ def _parse_units(self, input_string, as_delta=True):

return ret

def _eval_token(self, token, case_sensitive=True, use_decimal=False, **values):
def _eval_token(self, token, case_sensitive=None, use_decimal=False, **values):

# TODO: remove this code when use_decimal is deprecated
if use_decimal:
Expand Down Expand Up @@ -1156,7 +1169,7 @@ def _eval_token(self, token, case_sensitive=True, use_decimal=False, **values):
raise Exception("unknown token type")

def parse_pattern(
self, input_string, pattern, case_sensitive=True, use_decimal=False, many=False
self, input_string, pattern, case_sensitive=None, use_decimal=False, many=False
):
"""Parse a string with a given regex pattern and returns result.
Expand All @@ -1167,7 +1180,7 @@ def parse_pattern(
pattern_string:
The regex parse string
case_sensitive :
(Default value = True)
(Default value = None, which uses registry setting)
use_decimal :
(Default value = False)
many :
Expand Down Expand Up @@ -1212,7 +1225,7 @@ def parse_pattern(
return results

def parse_expression(
self, input_string, case_sensitive=True, use_decimal=False, **values
self, input_string, case_sensitive=None, use_decimal=False, **values
):
"""Parse a mathematical expression including units and return a quantity object.
Expand All @@ -1224,7 +1237,7 @@ def parse_expression(
input_string :
case_sensitive :
(Default value = True)
(Default value = None, which uses registry setting)
use_decimal :
(Default value = False)
**values :
Expand Down Expand Up @@ -1289,13 +1302,13 @@ def __init__(
# base units on multiplication and division.
self.autoconvert_offset_to_baseunit = autoconvert_offset_to_baseunit

def _parse_units(self, input_string, as_delta=None):
def _parse_units(self, input_string, as_delta=None, case_sensitive=None):
"""
"""
if as_delta is None:
as_delta = self.default_as_delta

return super()._parse_units(input_string, as_delta)
return super()._parse_units(input_string, as_delta, case_sensitive)

def _define(self, definition):
"""Add unit to the registry.
Expand Down Expand Up @@ -2099,6 +2112,8 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry):
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)
"""

def __init__(
Expand All @@ -2114,6 +2129,7 @@ def __init__(
preprocessors=None,
fmt_locale=None,
non_int_type=float,
case_sensitive=True,
):

super().__init__(
Expand All @@ -2128,6 +2144,7 @@ def __init__(
preprocessors=preprocessors,
fmt_locale=fmt_locale,
non_int_type=non_int_type,
case_sensitive=case_sensitive,
)

def pi_theorem(self, quantities):
Expand Down
8 changes: 8 additions & 0 deletions pint/testsuite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ def assertQuantityAlmostEqual(self, first, second, rtol=1e-07, atol=0, msg=None)
self.assertLessEqual(abs(m1 - m2), atol + rtol * abs(m2), msg=msg)


class CaseInsensitveQuantityTestCase(QuantityTestCase):
@classmethod
def setUpClass(cls):
cls.ureg = UnitRegistry(case_sensitive=False)
cls.Q_ = cls.ureg.Quantity
cls.U_ = cls.ureg.Unit


def testsuite():
"""A testsuite that has all the pint tests."""
suite = unittest.TestLoader().discover(os.path.dirname(__file__))
Expand Down
44 changes: 43 additions & 1 deletion pint/testsuite/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)
from pint.compat import np
from pint.registry import LazyRegistry, UnitRegistry
from pint.testsuite import QuantityTestCase, helpers
from pint.testsuite import CaseInsensitveQuantityTestCase, QuantityTestCase, helpers
from pint.testsuite.parameterized import ParameterizedTestCase
from pint.util import ParserHelper, UnitsContainer

Expand Down Expand Up @@ -704,6 +704,48 @@ def test_parse_pattern_many_results_two_units(self):
],
)

def test_case_sensitivity(self):
ureg = self.ureg
# Default
self.assertRaises(UndefinedUnitError, ureg.parse_units, "Meter")
self.assertRaises(UndefinedUnitError, ureg.parse_units, "j")
# Force True
self.assertRaises(
UndefinedUnitError, ureg.parse_units, "Meter", case_sensitive=True
)
self.assertRaises(
UndefinedUnitError, ureg.parse_units, "j", case_sensitive=True
)
# Force False
self.assertEqual(
ureg.parse_units("Meter", case_sensitive=False), UnitsContainer(meter=1)
)
self.assertEqual(
ureg.parse_units("j", case_sensitive=False), UnitsContainer(joule=1)
)


class TestCaseInsensitiveRegistry(CaseInsensitveQuantityTestCase):
def test_case_sensitivity(self):
ureg = self.ureg
# Default
self.assertEqual(ureg.parse_units("Meter"), UnitsContainer(meter=1))
self.assertEqual(ureg.parse_units("j"), UnitsContainer(joule=1))
# Force True
self.assertRaises(
UndefinedUnitError, ureg.parse_units, "Meter", case_sensitive=True
)
self.assertRaises(
UndefinedUnitError, ureg.parse_units, "j", case_sensitive=True
)
# Force False
self.assertEqual(
ureg.parse_units("Meter", case_sensitive=False), UnitsContainer(meter=1)
)
self.assertEqual(
ureg.parse_units("j", case_sensitive=False), UnitsContainer(joule=1)
)


class TestCompatibleUnits(QuantityTestCase):
FORCE_NDARRAY = False
Expand Down

0 comments on commit 1161c8b

Please sign in to comment.