From bf683077e2541109a2f4fed3a66c04e3d4b78180 Mon Sep 17 00:00:00 2001 From: zwimer Date: Sat, 2 Nov 2024 00:53:21 -0400 Subject: [PATCH 1/4] Add small_formatting argument to file_size --- src/human_readable/files.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/human_readable/files.py b/src/human_readable/files.py index e57a344..a543548 100644 --- a/src/human_readable/files.py +++ b/src/human_readable/files.py @@ -2,7 +2,7 @@ def file_size( - value: int, binary: bool = False, gnu: bool = False, formatting: str = ".1f" + value: int, binary: bool = False, gnu: bool = False, formatting: str = ".1f", small_formatting: str = "", ) -> str: """Return human-readable file size. @@ -12,12 +12,16 @@ def file_size( `10**3`. If ``gnu`` is True, the binary argument is ignored and GNU-style (``ls -sh`` style) prefixes are used (K, M) with the `2**10` definition. Non-gnu modes are compatible with jinja2's ``filesizeformat`` filter. + small_formatting is used instead of formatting when the number of bytes + is small enough that the applied suffix is B / Byte / Bytes, since files + cannot have a decimal number of bytes in a file size Args: value: size number. binary: binary format. Defaults to False. gnu: GNU format. Defaults to False. formatting: format pattern. Defaults to ".1f". + small_formatting: format pattern for small values. Defaults to "". Returns: str: file size in natural language. @@ -30,18 +34,19 @@ def file_size( suffixes = (" kB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB") base = 1024 if (gnu or binary) else 1000 + fmt = small_formatting if value < base else formatting if value == 1 and not gnu: - return f"{1:{formatting}} Byte" + return f"{1:{fmt}} Byte" if value < base and not gnu: - return f"{value:{formatting}} Bytes" + return f"{value:{fmt}} Bytes" if value < base and gnu: - return f"{value:{formatting}}B" + return f"{value:{fmt}}B" byte_size = float(value) suffix = "" for i, suffix in enumerate(suffixes): unit = base ** (i + 2) if byte_size < unit: - return f"{base * byte_size / unit:{formatting}}{suffix}" - return f"{base * byte_size / unit:{formatting}}{suffix}" + return f"{base * byte_size / unit:{fmt}}{suffix}" + return f"{base * byte_size / unit:{fmt}}{suffix}" From 2a29af4a0c8e6166feaeaec23f4c67654dde3453 Mon Sep 17 00:00:00 2001 From: zwimer Date: Tue, 5 Nov 2024 18:41:38 -0500 Subject: [PATCH 2/4] Cleanup file_size and add extra documentation --- src/human_readable/files.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/human_readable/files.py b/src/human_readable/files.py index a543548..447ff15 100644 --- a/src/human_readable/files.py +++ b/src/human_readable/files.py @@ -2,7 +2,11 @@ def file_size( - value: int, binary: bool = False, gnu: bool = False, formatting: str = ".1f", small_formatting: str = "", + value: int, + binary: bool = False, + gnu: bool = False, + formatting: str = ".1f", + small_formatting: str = "", ) -> str: """Return human-readable file size. @@ -20,8 +24,8 @@ def file_size( value: size number. binary: binary format. Defaults to False. gnu: GNU format. Defaults to False. - formatting: format pattern. Defaults to ".1f". - small_formatting: format pattern for small values. Defaults to "". + formatting: format pattern (applied to a float). Defaults to ".1f". + small_formatting: format pattern for small values (applied to an int). Defaults to "". Returns: str: file size in natural language. @@ -34,19 +38,18 @@ def file_size( suffixes = (" kB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB") base = 1024 if (gnu or binary) else 1000 - fmt = small_formatting if value < base else formatting if value == 1 and not gnu: - return f"{1:{fmt}} Byte" + return f"{1:{small_formatting}} Byte" if value < base and not gnu: - return f"{value:{fmt}} Bytes" + return f"{value:{small_formatting}} Bytes" if value < base and gnu: - return f"{value:{fmt}}B" + return f"{value:{small_formatting}}B" byte_size = float(value) suffix = "" for i, suffix in enumerate(suffixes): unit = base ** (i + 2) if byte_size < unit: - return f"{base * byte_size / unit:{fmt}}{suffix}" - return f"{base * byte_size / unit:{fmt}}{suffix}" + return f"{base * byte_size / unit:{formatting}}{suffix}" + return f"{base * byte_size / unit:{formatting}}{suffix}" From cdb0a1d1f4770595a9abd492b4d48d284485feb6 Mon Sep 17 00:00:00 2001 From: zwimer Date: Tue, 5 Nov 2024 18:43:21 -0500 Subject: [PATCH 3/4] Update test cases for new file_size --- tests/unit/test_files.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_files.py b/tests/unit/test_files.py index 8a663f7..d8887df 100644 --- a/tests/unit/test_files.py +++ b/tests/unit/test_files.py @@ -3,14 +3,14 @@ import pytest -import human_readable.files as files +from human_readable import files @pytest.mark.parametrize( "params, expected", [ - (1, "1.0 Byte"), # unit number - (300, "300.0 Bytes"), # hundreds number + (1, "1 Byte"), # unit number + (300, "300 Bytes"), # hundreds number (2900000, "2.9 MB"), # millions number (2000000000, "2.0 GB"), # billions number (10**26 * 30, "3000.0 YB"), # giant number @@ -24,8 +24,8 @@ def test_file_size(params: int, expected: str) -> None: @pytest.mark.parametrize( "params, expected", [ - ((1, True), "1.0 Byte"), # unit number - ((300, True), "300.0 Bytes"), # hundreds number + ((1, True), "1 Byte"), # unit number + ((300, True), "300 Bytes"), # hundreds number ((2900000, True), "2.8 MiB"), # millions number ((2000000000, True), "1.9 GiB"), # billions number ((10**26 * 30, True), "2481.5 YiB"), # giant number @@ -39,8 +39,8 @@ def test_file_size_binary(params: tuple[int, bool], expected: str) -> None: @pytest.mark.parametrize( "params, expected", [ - ((1, False, True), "1.0B"), # unit number - ((300, False, True), "300.0B"), # hundreds number + ((1, False, True), "1B"), # unit number + ((300, False, True), "300B"), # hundreds number ((2900000, False, True), "2.8M"), # millions number ((2000000000, False, True), "1.9G"), # billions number ((10**26 * 30, False, True), "2481.5Y"), # giant number @@ -54,9 +54,14 @@ def test_file_size_gnu(params: tuple[int, bool, bool], expected: str) -> None: @pytest.mark.parametrize( "params, expected", [ - ((1, False, True, ".0f"), "1B"), # unit number - ((300, True, False, ".2f"), "300.00 Bytes"), # hundreds number - ((2900000, False, True, ".3f"), "2.766M"), # millions number + ((1, False, True, ".3f", ".1f"), "1.0B"), # unit number (small formatting) + ((999, False, False, ".3f", ".1f"), "999.0 Bytes"), # hundreds number (small formatting) + ((1000, False, False, ".3f", ".1f"), "1.000 KB"), # hundreds number (small formatting boundary) + ((1023, False, True, ".3f", ".1f"), "1023.0B"), # hundreds number (small formatting boundary) + ((1024, False, True, ".3f", ".1f"), "1.000K"), # hundreds number (small formatting boundary) + ((1023, True, False, ".3f", ".1f"), "1023.0 Bytes"), # hundreds number (small formatting boundary) + ((1024, True, False, ".3f", ".1f"), "1.000 KiB"), # hundreds number (small formatting boundary) + ((2900000, False, True, ".3f"), "2.766M"), # millions number (large formatting) ( (2000000000, True, False, ".3f"), "1.863 GiB", From 2b28dcfc2cfe33a683fdfbcf4f9186206ec517ee Mon Sep 17 00:00:00 2001 From: zwimer Date: Tue, 5 Nov 2024 18:43:57 -0500 Subject: [PATCH 4/4] Bug fix in file_size suffixes: kB -> KB --- src/human_readable/files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/human_readable/files.py b/src/human_readable/files.py index 447ff15..b06bf0b 100644 --- a/src/human_readable/files.py +++ b/src/human_readable/files.py @@ -35,7 +35,7 @@ def file_size( elif binary: suffixes = (" KiB", " MiB", " GiB", " TiB", " PiB", " EiB", " ZiB", " YiB") else: - suffixes = (" kB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB") + suffixes = (" KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB") base = 1024 if (gnu or binary) else 1000