Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow an optional separator splitting the value and unit of the result of ByteSize.human_readable. #8706

Merged
merged 5 commits into from Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 7 additions & 4 deletions pydantic/types.py
Expand Up @@ -1740,6 +1740,8 @@ class MyModel(BaseModel):
#> 44.4PiB
print(m.size.human_readable(decimal=True))
#> 50.0PB
print(m.size.human_readable(separator=' '))
#> 44.4 PiB

print(m.size.to('TiB'))
#> 45474.73508864641
Expand Down Expand Up @@ -1818,12 +1820,13 @@ def _validate(cls, __input_value: Any, _: core_schema.ValidationInfo) -> ByteSiz

return cls(int(float(scalar) * unit_mult))

def human_readable(self, decimal: bool = False) -> str:
def human_readable(self, decimal: bool = False, separator: str = '') -> str:
"""Converts a byte size to a human readable string.

Args:
decimal: If True, use decimal units (e.g. 1000 bytes per KB). If False, use binary units
(e.g. 1024 bytes per KiB).
separator: Split the value and unit with a specific separator.
sydney-runkle marked this conversation as resolved.
Show resolved Hide resolved

Returns:
A human readable string representation of the byte size.
Expand All @@ -1841,12 +1844,12 @@ def human_readable(self, decimal: bool = False) -> str:
for unit in units:
if abs(num) < divisor:
if unit == 'B':
return f'{num:0.0f}{unit}'
return f'{num:0.0f}{separator}{unit}'
else:
return f'{num:0.1f}{unit}'
return f'{num:0.1f}{separator}{unit}'
num /= divisor

return f'{num:0.1f}{final_unit}'
return f'{num:0.1f}{separator}{final_unit}'

def to(self, unit: str) -> float:
"""Converts a byte size to another unit, including both byte and bit units.
Expand Down
29 changes: 15 additions & 14 deletions tests/test_types.py
Expand Up @@ -4436,23 +4436,23 @@ class FrozenSetModel(BaseModel):


@pytest.mark.parametrize(
'input_value,output,human_bin,human_dec',
'input_value,output,human_bin,human_dec,human_sep',
(
(1, 1, '1B', '1B'),
('1', 1, '1B', '1B'),
('1.0', 1, '1B', '1B'),
('1b', 1, '1B', '1B'),
('1.5 KB', int(1.5e3), '1.5KiB', '1.5KB'),
('1.5 K', int(1.5e3), '1.5KiB', '1.5KB'),
('1.5 MB', int(1.5e6), '1.4MiB', '1.5MB'),
('1.5 M', int(1.5e6), '1.4MiB', '1.5MB'),
('5.1kib', 5222, '5.1KiB', '5.2KB'),
('6.2EiB', 7148113328562451456, '6.2EiB', '7.1EB'),
('8bit', 1, '1B', '1B'),
('1kbit', 125, '125B', '125B'),
(1, 1, '1B', '1B', '1 B'),
('1', 1, '1B', '1B', '1 B'),
('1.0', 1, '1B', '1B', '1 B'),
('1b', 1, '1B', '1B', '1 B'),
('1.5 KB', int(1.5e3), '1.5KiB', '1.5KB', '1.5 KiB'),
('1.5 K', int(1.5e3), '1.5KiB', '1.5KB', '1.5 KiB'),
('1.5 MB', int(1.5e6), '1.4MiB', '1.5MB', '1.4 MiB'),
('1.5 M', int(1.5e6), '1.4MiB', '1.5MB', '1.4 MiB'),
('5.1kib', 5222, '5.1KiB', '5.2KB', '5.1 KiB'),
('6.2EiB', 7148113328562451456, '6.2EiB', '7.1EB', '6.2 EiB'),
('8bit', 1, '1B', '1B', '1 B'),
('1kbit', 125, '125B', '125B', '125 B'),
),
)
def test_bytesize_conversions(input_value, output, human_bin, human_dec):
def test_bytesize_conversions(input_value, output, human_bin, human_dec, human_sep):
class Model(BaseModel):
size: ByteSize

Expand All @@ -4462,6 +4462,7 @@ class Model(BaseModel):

assert m.size.human_readable() == human_bin
assert m.size.human_readable(decimal=True) == human_dec
assert m.size.human_readable(separator=' ') == human_sep


def test_bytesize_to():
Expand Down