diff --git a/src/quantityfield/helper.py b/src/quantityfield/helper.py index b4286ee..442834d 100644 --- a/src/quantityfield/helper.py +++ b/src/quantityfield/helper.py @@ -10,10 +10,14 @@ def check_matching_unit_dimension( If not :raise DimensionalityError """ - base_unit = getattr(ureg, base_units) + # create a pint quantity by multiplying unit with magnitude of 1 + base_quant = 1 * base_unit for unit_string in units_to_check: unit = getattr(ureg, unit_string) - if unit.dimensionality != base_unit.dimensionality: - raise DimensionalityError(base_unit, unit) + # try to convert base qunatity to new unit, this also work for ureg.context + try: + base_quant.to(unit) + except DimensionalityError as e: + raise DimensionalityError(base_unit, unit) from e diff --git a/tests/test_helper.py b/tests/test_helper.py index 23cc9ba..f570cbe 100644 --- a/tests/test_helper.py +++ b/tests/test_helper.py @@ -1,6 +1,6 @@ from django.test import TestCase -from pint import DimensionalityError +from pint import Context, DimensionalityError import quantityfield.fields as fields import quantityfield.helper as helper @@ -28,3 +28,80 @@ def test_get_prep_value(self): field = fields.IntegerQuantityField("meter") with self.assertRaises(ValueError): field.get_prep_value("foobar") + + +class TestContextHandling(TestCase): + """Class to test ureg.context for compatible units as described in issue #99. + pint allows users to define a special context for unit conversions, + e.g. on earth a mass can directly be converted to a force given the + acceleration 'constant' on earth. + We will test the unit compatibility via the helper function for both + both context activated globally via ureg and within a with block. Finally + test the conversion integrated inside an IntegerQuantityField. Also the + negatives are tested: without the context, mass should not be accepted + as a matching unit for force. + """ + + def setUp(self): + """Setup a pint context in the UnitRegistry.""" + # Define a context where mass is equated to force via earth's + # standard acceleration of gravity and vice versa + # (https://en.wikipedia.org/wiki/Standard_gravity) + self.context = Context("earth") + # mass -> force + self.context.add_transformation( + "[mass]", "[force]", lambda ureg, x: x * ureg.gravity + ) + # force -> mass + self.context.add_transformation( + "[force]", "[mass]", lambda ureg, x: x / ureg.gravity + ) + ureg.add_context(self.context) + + def test_context_global(self): + """Activate ureg.context globally and test conversion compatibility directly.""" + # Activate context globally and test + ureg.enable_contexts("earth") + helper.check_matching_unit_dimension(ureg, "kg", ["newton", "kN", "ton"]) + ureg.disable_contexts() + + def test_context_with_block(self): + """Activate ureg.context in with block and test conversion compatibility + directly.""" + # Use context with the 'with' statement + with ureg.context("earth"): + helper.check_matching_unit_dimension(ureg, "kg", ["newton", "kN", "ton"]) + + def test_invalid_context(self): + """Negative test: Conversion mass to force should fail without context.""" + with self.assertRaises(DimensionalityError): + helper.check_matching_unit_dimension(ureg, "kg", ["newton", "kN", "ton"]) + + def test_field_w_context_global(self): + """Negative test: Conversion mass to force should fail without context.""" + ureg.enable_contexts("earth") + self.field = fields.IntegerQuantityField( + base_units="kg", unit_choices=["newton", "kN", "ton"] + ) + ureg.disable_contexts() + + def test_field_w_context_block(self): + """Activate ureg.context globally and test conversion compatibility complete + Field.""" + with ureg.context("earth"): + self.field = fields.IntegerQuantityField( + base_units="kg", unit_choices=["newton", "kN", "ton"] + ) + + def test_invalid_field_wo_context(self): + """Negative test: Conversion mass to force should fail without context.""" + with self.assertRaises(DimensionalityError): + self.field = fields.IntegerQuantityField( + base_units="kg", unit_choices=["newton", "kN", "ton"] + ) + + def tearDown(self): + """Disable and remove the contexts to not interfere with other tests.""" + # Clean up by disabling and removing the context + ureg.disable_contexts() + ureg.remove_context("earth")