Coverage for utilities/string.py: 21%
36 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1import re
3__all__ = (
4 'enum_key',
5 'humanize_duration',
6 'remove_linebreaks',
7 'title',
8 'trailing_slash',
9)
12def humanize_duration(value):
13 """
14 Express a timedelta in a human-friendly format. Example: 1h 5m 23s. Durations of a second or
15 more are rounded to whole seconds; shorter durations are rounded to the millisecond (e.g.
16 0.43s). A negative duration is rendered with a leading minus sign, so that an anomalous value
17 remains recognizable as one. Returns an empty string for None; zero renders as "0s".
18 """
19 if value is None:
20 return ''
22 total_seconds = value.total_seconds()
23 magnitude = abs(total_seconds)
25 # Render sub-second durations to the millisecond, as rounding them to whole seconds would
26 # report every short-lived duration as zero. Trailing zeros are stripped.
27 if 0 < magnitude < 1:
28 rendered = f'{magnitude:.3f}'.rstrip('0').rstrip('.')
29 # A magnitude below a millisecond has no representation here, so fall through to "0s".
30 # Rounding up to a whole second (e.g. 0.9996) likewise falls through, to "1s".
31 if rendered not in ('0', '1'):
32 return f'-{rendered}s' if total_seconds < 0 else f'{rendered}s'
34 # Round to whole seconds and decompose
35 days, remainder = divmod(round(magnitude), 86400)
36 hours, remainder = divmod(remainder, 3600)
37 minutes, seconds = divmod(remainder, 60)
39 ret = ''
40 if days:
41 ret += f'{days}d '
42 if hours:
43 ret += f'{hours}h '
44 if minutes:
45 ret += f'{minutes}m '
46 if seconds or not ret:
47 ret += f'{seconds}s'
48 ret = ret.strip()
50 # Zero carries no sign, however the original value was signed
51 if total_seconds < 0 and ret != '0s':
52 ret = f'-{ret}'
53 return ret
56def enum_key(value):
57 """
58 Convert the given value to a string suitable for use as an Enum key.
59 """
60 value = str(value).upper()
61 return re.sub(r'[^_A-Z0-9]', '_', value)
64def remove_linebreaks(value):
65 """
66 Remove all line breaks from a string and return the result. Useful for log sanitization purposes.
67 """
68 return value.replace('\n', '').replace('\r', '')
71def title(value):
72 """
73 Improved implementation of str.title(); retains all existing uppercase letters.
74 """
75 return ' '.join([w[0].upper() + w[1:] for w in str(value).split()])
78def trailing_slash(value):
79 """
80 Remove a leading slash (if any) and include a trailing slash, except for empty strings.
81 """
82 return f'{value.strip("/")}/' if value else ''