Skip to content

Commit 77d083f

Browse files
authored
Merge pull request #188 from rjgpacheco/feat/format_clamped
Adds humanize.number.clamp
2 parents a0f03c1 + eb18683 commit 77d083f

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

src/humanize/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33

44
from humanize.filesize import naturalsize
55
from humanize.i18n import activate, deactivate, thousands_separator
6-
from humanize.number import apnumber, fractional, intcomma, intword, ordinal, scientific
6+
from humanize.number import (
7+
apnumber,
8+
clamp,
9+
fractional,
10+
intcomma,
11+
intword,
12+
ordinal,
13+
scientific,
14+
)
715
from humanize.time import (
816
naturaldate,
917
naturalday,
@@ -19,6 +27,7 @@
1927
"__version__",
2028
"activate",
2129
"apnumber",
30+
"clamp",
2231
"deactivate",
2332
"fractional",
2433
"intcomma",

src/humanize/number.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,66 @@ def scientific(value, precision=2):
363363
final_str = part1 + " x 10" + "".join(new_part2)
364364

365365
return final_str
366+
367+
368+
def clamp(value, format="{:}", floor=None, ceil=None, floor_token="<", ceil_token=">"):
369+
"""Returns number with the specified format, clamped between floor and ceil.
370+
371+
If the number is larger than ceil or smaller than floor, then the respective limit
372+
will be returned, formatted and prepended with a token specifying as such.
373+
374+
Examples:
375+
```pycon
376+
>>> clamp(123.456)
377+
'123.456'
378+
>>> clamp(0.0001, floor=0.01)
379+
'<0.01'
380+
>>> clamp(0.99, format="{:.0%}", ceil=0.99)
381+
'99%'
382+
>>> clamp(0.999, format="{:.0%}", ceil=0.99)
383+
'>99%'
384+
>>> clamp(1, format=intword, floor=1e6, floor_token="under ")
385+
'under 1.0 million'
386+
>>> clamp(None) is None
387+
True
388+
389+
```
390+
391+
Args:
392+
value (int, float): Input number.
393+
format (str OR callable): Can either be a formatting string, or a callable
394+
function than receives value and returns a string.
395+
floor (int, float): Smallest value before clamping.
396+
ceil (int, float): Largest value before clamping.
397+
floor_token (str): If value is smaller than floor, token will be prepended
398+
to output.
399+
ceil_token (str): If value is larger than ceil, token will be prepended
400+
to output.
401+
402+
Returns:
403+
str: Formatted number. The output is clamped between the indicated floor and
404+
ceil. If the number if larger than ceil or smaller than floor, the output will
405+
be prepended with a token indicating as such.
406+
407+
"""
408+
if value is None:
409+
return None
410+
411+
if floor is not None and value < floor:
412+
value = floor
413+
token = floor_token
414+
elif ceil is not None and value > ceil:
415+
value = ceil
416+
token = ceil_token
417+
else:
418+
token = ""
419+
420+
if isinstance(format, str):
421+
return token + format.format(value)
422+
elif callable(format):
423+
return token + format(value)
424+
else:
425+
raise ValueError(
426+
"Invalid format. Must be either a valid formatting string, or a function "
427+
"that accepts value and returns a string."
428+
)

tests/test_number.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,20 @@ def test_fractional(test_input, expected):
154154
)
155155
def test_scientific(test_args, expected):
156156
assert humanize.scientific(*test_args) == expected
157+
158+
159+
@pytest.mark.parametrize(
160+
"test_args, expected",
161+
[
162+
([1], "1"),
163+
([None], None),
164+
([0.0001, "{:.0%}"], "0%"),
165+
([0.0001, "{:.0%}", 0.01], "<1%"),
166+
([0.9999, "{:.0%}", None, 0.99], ">99%"),
167+
([0.0001, "{:.0%}", 0.01, None, "under ", None], "under 1%"),
168+
([0.9999, "{:.0%}", None, 0.99, None, "above "], "above 99%"),
169+
([1, humanize.intword, 1e6, None, "under "], "under 1.0 million"),
170+
],
171+
)
172+
def test_clamp(test_args, expected):
173+
assert humanize.clamp(*test_args) == expected

0 commit comments

Comments
 (0)