Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cfunits/test/test_Units.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
69 changes: 68 additions & 1 deletion cfunits/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@
_cv_free.restype = None

_UT_ASCII = 0
_UT_UTF8 = 2
_UT_NAMES = 4
_UT_DEFINITION = 8

Expand Down Expand Up @@ -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
# --------------------------------------------------------------------
Expand Down Expand Up @@ -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
Expand All @@ -2052,6 +2093,32 @@ def formatted(self, names=None, definition=None):

return out.decode("utf-8")


@classmethod
def new_unit(cls, name):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc string please!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added documentation in my branch.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case this is sufficient, please resolve the conversation.

"""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.
Expand Down
2 changes: 2 additions & 0 deletions docs/source/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,5 @@ ideas, code, and documentation to the cfunits library:
* Jonathan Gregory
* Lars Bärring
* Sadie Bartholomew
* Reiner Jung