Repository: hgrecco/pint

Issue:
A check function that's not a decorator
Is there a way we could implement a check function for stand-alone use, not just for wrapping functions?

```python
>>> from pint import _DEFAULT_REGISTRY
>>> ureg = _DEFAULT_REGISTRY
>>> Q_ = ureg.Quantity
>>> distance = Q_('1 meter')
>>> time = Q_('1 second')
>>> ureg.check(distance, '[length]')
True
>>> ureg.check(time, '[length]')
False
```

Discussion:

Patch:
diff --git a/docs/wrapping.rst b/docs/wrapping.rst
index dd9a39f..6ce6411 100644
--- a/docs/wrapping.rst
+++ b/docs/wrapping.rst
@@ -246,3 +246,12 @@ In the decorator format:
     ... def pendulum_period(length):
     ...     return 2*math.pi*math.sqrt(length/G)
 
+If you just want to check the dimensionality of a quantity, you can do so with the built-in 'check' function.
+
+.. doctest::
+
+    >>> distance = 1 * ureg.m
+    >>> distance.check('[length]')
+    True
+    >>> distance.check('[time]')
+    False
diff --git a/pint/quantity.py b/pint/quantity.py
index 88bfdac..ef25509 100644
--- a/pint/quantity.py
+++ b/pint/quantity.py
@@ -294,6 +294,11 @@ class _Quantity(PrettyIPython, SharedRegistryObject):
 
         return self._dimensionality
 
+    def check(self, dimension):
+        """Return true if the quantity's dimension matches passed dimension.
+        """
+        return self.dimensionality == dimension
+
     @classmethod
     def from_tuple(cls, tup):
         return cls(tup[0], UnitsContainer(tup[1]))
diff --git a/pint/registry_helpers.py b/pint/registry_helpers.py
index 4a8b862..5ac0dbd 100644
--- a/pint/registry_helpers.py
+++ b/pint/registry_helpers.py
@@ -130,6 +130,20 @@ def _parse_wrap_args(args, registry=None):
 
     return _converter
 
+def _apply_defaults(func, args, kwargs):
+    """Apply default keyword arguments.
+
+    Named keywords may have been left blank. This function applies the default
+    values so that every argument is defined.
+    """
+
+    sig = signature(func)
+    bound_arguments = sig.bind(*args)
+    for param in sig.parameters.values():
+        if param.name not in bound_arguments.arguments:
+            bound_arguments.arguments[param.name] = param.default
+    args = [bound_arguments.arguments[key] for key in sig.parameters.keys()]
+    return args, {} 
 
 def wraps(ureg, ret, args, strict=True):
     """Wraps a function to become pint-aware.
@@ -171,18 +185,7 @@ def wraps(ureg, ret, args, strict=True):
         @functools.wraps(func, assigned=assigned, updated=updated)
         def wrapper(*values, **kw):
 
-
-            # Named keywords may have been left blank. Wherever the named keyword is blank,
-            # fill it in with the default value.
-            sig = signature(func)
-            bound_arguments = sig.bind(*values, **kw)
-
-            for param in sig.parameters.values():
-                if param.name not in bound_arguments.arguments:
-                    bound_arguments.arguments[param.name] = param.default
-
-            values = [bound_arguments.arguments[key] for key in sig.parameters.keys()]
-            kw = {}
+            values, kw = _apply_defaults(func, values, kw)
                 
             # In principle, the values are used as is
             # When then extract the magnitudes when needed.
@@ -228,13 +231,17 @@ def check(ureg, *args):
 
         @functools.wraps(func, assigned=assigned, updated=updated)
         def wrapper(*values, **kwargs):
-            for dim, value in zip_longest(dimensions, values):
+            values, kwargs = _apply_defaults(func, values, kwargs)
+            if len(dimensions) > len(values):
+                raise TypeError("%s takes %i parameters, but %i dimensions were passed"
+                % (func.__name__, len(values), len(dimensions)))
+            for dim, value in zip(dimensions, values):
 
                 if dim is None:
                     continue
 
-                val_dim = ureg.get_dimensionality(value)
-                if val_dim != dim:
+                if not ureg.Quantity(value).check(dim):
+                    val_dim = ureg.get_dimensionality(value)
                     raise DimensionalityError(value, 'a quantity of',
                                               val_dim, dim)
             return func(*values, **kwargs)


Test patch:
diff --git a/pint/testsuite/test_issues.py b/pint/testsuite/test_issues.py
index 0cefce9..4dbcb98 100644
--- a/pint/testsuite/test_issues.py
+++ b/pint/testsuite/test_issues.py
@@ -625,3 +625,38 @@ class TestIssuesNP(QuantityTestCase):
 
         d2 = get_displacement(Q_(2, 's'), Q_(1, 'deg/s'))
         self.assertAlmostEqual(d2, Q_(2,' deg'))
+
+    def test_issue655a(self):
+        ureg = UnitRegistry()
+        distance = 1 * ureg.m
+        time = 1 * ureg.s
+        velocity = distance / time
+        self.assertEqual(distance.check('[length]'), True)
+        self.assertEqual(distance.check('[time]'), False)
+        self.assertEqual(velocity.check('[length] / [time]'), True)
+        self.assertEqual(velocity.check('1 / [time] * [length]'), True)
+
+    def test_issue(self):
+        import math
+        try:
+            from inspect import signature
+        except ImportError:
+            # Python2 does not have the inspect library. Import the backport
+            from funcsigs import signature
+
+        ureg = UnitRegistry()
+        Q_ = ureg.Quantity
+        @ureg.check('[length]', '[length]/[time]^2')
+        def pendulum_period(length, G=Q_(1, 'standard_gravity')):
+            print(length)
+            return (2*math.pi*(length/G)**.5).to('s')
+        l = 1 * ureg.m
+        # Assume earth gravity
+        t = pendulum_period(l)
+        self.assertAlmostEqual(t, Q_('2.0064092925890407 second'))
+        # Use moon gravity
+        moon_gravity = Q_(1.625, 'm/s^2')
+        t = pendulum_period(l, moon_gravity)
+        self.assertAlmostEqual(t, Q_('4.928936075204336 second'))
+
+
diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py
index 0e7b17f..b9e3b06 100644
--- a/pint/testsuite/test_unit.py
+++ b/pint/testsuite/test_unit.py
@@ -437,13 +437,13 @@ class TestRegistry(QuantityTestCase):
 
         g2 = ureg.check('[speed]')(gfunc)
         self.assertRaises(DimensionalityError, g2, 3.0, 1)
-        self.assertRaises(DimensionalityError, g2, 2 * ureg.parsec)
+        self.assertRaises(TypeError, g2, 2 * ureg.parsec)
         self.assertRaises(DimensionalityError, g2, 2 * ureg.parsec, 1.0)
         self.assertEqual(g2(2.0 * ureg.km / ureg.hour, 2), 1 * ureg.km / ureg.hour)
 
         g3 = ureg.check('[speed]', '[time]', '[mass]')(gfunc)
-        self.assertRaises(DimensionalityError, g3, 1 * ureg.parsec, 1 * ureg.angstrom)
-        self.assertRaises(DimensionalityError, g3, 1 * ureg.parsec, 1 * ureg.angstrom, 1 * ureg.kilogram)
+        self.assertRaises(TypeError, g3, 1 * ureg.parsec, 1 * ureg.angstrom)
+        self.assertRaises(TypeError, g3, 1 * ureg.parsec, 1 * ureg.angstrom, 1 * ureg.kilogram)
 
     def test_to_ref_vs_to(self):
         self.ureg.autoconvert_offset_to_baseunit = True