Skip to content

Commit

Permalink
Merge pull request #188 from rjgpacheco/feat/format_clamped
Browse files Browse the repository at this point in the history
Adds humanize.number.clamp
  • Loading branch information
hugovk committed Mar 20, 2021
2 parents a0f03c1 + eb18683 commit 77d083f
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/humanize/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@

from humanize.filesize import naturalsize
from humanize.i18n import activate, deactivate, thousands_separator
from humanize.number import apnumber, fractional, intcomma, intword, ordinal, scientific
from humanize.number import (
apnumber,
clamp,
fractional,
intcomma,
intword,
ordinal,
scientific,
)
from humanize.time import (
naturaldate,
naturalday,
Expand All @@ -19,6 +27,7 @@
"__version__",
"activate",
"apnumber",
"clamp",
"deactivate",
"fractional",
"intcomma",
Expand Down
63 changes: 63 additions & 0 deletions src/humanize/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,66 @@ def scientific(value, precision=2):
final_str = part1 + " x 10" + "".join(new_part2)

return final_str


def clamp(value, format="{:}", floor=None, ceil=None, floor_token="<", ceil_token=">"):
"""Returns number with the specified format, clamped between floor and ceil.
If the number is larger than ceil or smaller than floor, then the respective limit
will be returned, formatted and prepended with a token specifying as such.
Examples:
```pycon
>>> clamp(123.456)
'123.456'
>>> clamp(0.0001, floor=0.01)
'<0.01'
>>> clamp(0.99, format="{:.0%}", ceil=0.99)
'99%'
>>> clamp(0.999, format="{:.0%}", ceil=0.99)
'>99%'
>>> clamp(1, format=intword, floor=1e6, floor_token="under ")
'under 1.0 million'
>>> clamp(None) is None
True
```
Args:
value (int, float): Input number.
format (str OR callable): Can either be a formatting string, or a callable
function than receives value and returns a string.
floor (int, float): Smallest value before clamping.
ceil (int, float): Largest value before clamping.
floor_token (str): If value is smaller than floor, token will be prepended
to output.
ceil_token (str): If value is larger than ceil, token will be prepended
to output.
Returns:
str: Formatted number. The output is clamped between the indicated floor and
ceil. If the number if larger than ceil or smaller than floor, the output will
be prepended with a token indicating as such.
"""
if value is None:
return None

if floor is not None and value < floor:
value = floor
token = floor_token
elif ceil is not None and value > ceil:
value = ceil
token = ceil_token
else:
token = ""

if isinstance(format, str):
return token + format.format(value)
elif callable(format):
return token + format(value)
else:
raise ValueError(
"Invalid format. Must be either a valid formatting string, or a function "
"that accepts value and returns a string."
)
17 changes: 17 additions & 0 deletions tests/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,20 @@ def test_fractional(test_input, expected):
)
def test_scientific(test_args, expected):
assert humanize.scientific(*test_args) == expected


@pytest.mark.parametrize(
"test_args, expected",
[
([1], "1"),
([None], None),
([0.0001, "{:.0%}"], "0%"),
([0.0001, "{:.0%}", 0.01], "<1%"),
([0.9999, "{:.0%}", None, 0.99], ">99%"),
([0.0001, "{:.0%}", 0.01, None, "under ", None], "under 1%"),
([0.9999, "{:.0%}", None, 0.99, None, "above "], "above 99%"),
([1, humanize.intword, 1e6, None, "under "], "under 1.0 million"),
],
)
def test_clamp(test_args, expected):
assert humanize.clamp(*test_args) == expected

0 comments on commit 77d083f

Please sign in to comment.