From 57b0ee626adb0a54cc378d3e48a8f2889808063b Mon Sep 17 00:00:00 2001 From: Matthias Arras Date: Sat, 10 Aug 2024 23:40:35 +0000 Subject: [PATCH 1/3] try unit conversion instead of literal dimensionality check #99 --- src/quantityfield/helper.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 From b1f0111314fd7b01095d04465864c179a82571f0 Mon Sep 17 00:00:00 2001 From: Matthias Arras Date: Sun, 11 Aug 2024 16:51:15 +0000 Subject: [PATCH 2/3] added tests for context --- tests/test_helper.py | 54 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/tests/test_helper.py b/tests/test_helper.py index 23cc9ba..8106c48 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,55 @@ def test_get_prep_value(self): field = fields.IntegerQuantityField("meter") with self.assertRaises(ValueError): field.get_prep_value("foobar") + + +class TestContextHandling(TestCase): + def setUp(self): + # Define a context where weight is equated to force + self.context = Context("earth") + self.context.add_transformation( + "[mass]", "[force]", lambda ureg, x: x * ureg.gravity + ) + self.context.add_transformation( + "[force]", "[mass]", lambda ureg, x: x / ureg.gravity + ) + ureg.add_context(self.context) + + def test_context_global(self): + # 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): + # 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): + with self.assertRaises(DimensionalityError): + helper.check_matching_unit_dimension(ureg, "kg", ["newton", "kN", "ton"]) + + def test_field_w_context_global(self): + 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): + with ureg.context("earth"): + self.field = fields.IntegerQuantityField( + base_units="kg", unit_choices=["newton", "kN", "ton"] + ) + + def test_invalid_field_wo_context(self): + with self.assertRaises(DimensionalityError): + self.field = fields.IntegerQuantityField( + base_units="kg", unit_choices=["newton", "kN", "ton"] + ) + + def tearDown(self): + # Clean up by disabling and removing the context + ureg.disable_contexts() + ureg.remove_context("earth") From 1013305ee8116cb9cf5b1796e5e5cde2a9c39cbd Mon Sep 17 00:00:00 2001 From: Matthias Arras Date: Fri, 23 Aug 2024 15:13:06 +0000 Subject: [PATCH 3/3] added doc strings to test --- tests/test_helper.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/test_helper.py b/tests/test_helper.py index 8106c48..f570cbe 100644 --- a/tests/test_helper.py +++ b/tests/test_helper.py @@ -31,33 +31,54 @@ def test_get_prep_value(self): 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): - # Define a context where weight is equated to force + """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"] @@ -65,18 +86,22 @@ def test_field_w_context_global(self): 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")