diff --git a/cfunits/test/test_Units.py b/cfunits/test/test_Units.py index cc3d005..98a55cb 100644 --- a/cfunits/test/test_Units.py +++ b/cfunits/test/test_Units.py @@ -140,6 +140,24 @@ def test_Units_equivalent(self): self.assertFalse(Units(" ").equivalent(Units(1))) self.assertFalse(Units("1").equivalent(Units(1))) + def test_Units_new_unit(self): + """Test the `new_unit` class method on `Units`.""" + + # can create a custom unit + u_pebbles = Units("pebbles") + + self.assertIsNotNone(u_pebbles) + self.assertIsTrue(u_pebbles.isvalid) + self.assertEqual(u_pebbles.formatted(), "pebbles") + + # use composed unit of a custom unit and an udunits unit + u_pebbles_m = Units("pebbles/m") + + self.assertIsNotNone(u_pebbles_m) + self.assertIsTrue(u_pebbles.isvalid) + # Note: formatting does not work currently + # self.assertEqual(u_pebbles.formatted(), "pebbles") + def test_Units_conform(self): """Tests the `conform` class method on `Units`.""" self.assertEqual(Units.conform(0.5, Units("km"), Units("m")), 500) diff --git a/cfunits/units.py b/cfunits/units.py index b1e4058..84ffbc5 100644 --- a/cfunits/units.py +++ b/cfunits/units.py @@ -185,6 +185,7 @@ _cv_free.restype = None _UT_ASCII = 0 +_UT_UTF8 = 2 _UT_NAMES = 4 _UT_DEFINITION = 8 @@ -344,6 +345,46 @@ def add_unit_alias(definition, symbol, singular, plural): # [-99, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]]) +# -------------------------------------------------------------------- +# Function to decode Udunits status codes to text +# -------------------------------------------------------------------- +def _decode_status(status): + match status: + case 0: + return "success" + case 1: + return "bad argument" + case 2: + return "already exists" + case 3: + return "no such unit" + case 4: + return "os error, see errno" + case 5: + return "units belong to different unit-systems" + case 6: + return "operation on the unit(s) is meaningless" + case 7: + return "unit-system doesn't have a unit named 'second'" + case 8: + return "error occurred while visiting a unit" + case 9: + return "unit can't be formatted in the desired manner" + case 10: + return "string unit representation contains syntax error" + case 11: + return "string unit representation contains unknown word" + case 12: + return "cannot open argument-specified unit database" + case 13: + return "cannot open environment-specified unit database" + case 14: + return "cannot open installed, default, unit database" + case 15: + return "error parsing unit specification" + case _: + return f"unknown error {status}!" + # -------------------------------------------------------------------- # Function to control Udunits error messages # -------------------------------------------------------------------- @@ -2043,7 +2084,7 @@ def formatted(self, names=None, definition=None): if _ut_format(ut_unit, _string_buffer, _sizeof_buffer, opts) != -1: out = _string_buffer.value else: - raise ValueError(f"Can't format unit {self!r}") + raise ValueError(f"Cannot format unit {self!r} cause: {_decode_status(_ut_get_status())}") if self.isreftime: out = str(out, "utf-8") # needs converting from byte-string @@ -2052,6 +2093,32 @@ def formatted(self, names=None, definition=None): return out.decode("utf-8") + + @classmethod + def new_unit(cls, name): + """Creates a new custom unit in the unit system. + + Returns the new unit. + + :Parameters: + name: str + name of the new unit. + + Returns + the newly created unit. + + **Examples** + >>> pebbles = Units.new_unit("pebbles") + + """ + base = _ut_new_base_unit(_ut_system) + + assert 0 == _ut_map_unit_to_name(base, name.encode("utf-8"), _UT_UTF8) + assert 0 == _ut_map_name_to_unit(name.encode("utf-8"), _UT_UTF8, base) + + return base + + @classmethod def conform(cls, x, from_units, to_units, inplace=False): """Conforms values to equivalent values in a compatible unit. diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index d1612f4..4b4151d 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -115,3 +115,5 @@ ideas, code, and documentation to the cfunits library: * Jonathan Gregory * Lars Bärring * Sadie Bartholomew +* Reiner Jung +