From a08cda30fb5476ea1b9c60839ce95adbfd6edf16 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 6 Aug 2025 12:02:51 +0200 Subject: [PATCH 1/4] ready for review --- supervision/annotators/utils.py | 30 +++++---- test/annotators/test_utils.py | 116 +++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 14 deletions(-) diff --git a/supervision/annotators/utils.py b/supervision/annotators/utils.py index 511c68de..f3f38fc3 100644 --- a/supervision/annotators/utils.py +++ b/supervision/annotators/utils.py @@ -2,6 +2,7 @@ from __future__ import annotations import textwrap from enum import Enum +from typing import Any import numpy as np @@ -151,30 +152,36 @@ def resolve_color( return get_color_by_index(color=color, idx=idx) -def wrap_text(text: str, max_line_length=None) -> list[str]: +def wrap_text(text: Any, max_line_length=None) -> list[str]: """ - Wraps text to the specified maximum line length, respecting existing newlines. - Uses the textwrap library for robust text wrapping. + Wrap `text` to the specified maximum line length, respecting existing + newlines. Falls back to str() if `text` is not already a string. Args: - text (str): The text to wrap. + text (Any): The text (or object) to wrap. + max_line_length (int | None): Maximum width for each wrapped line. Returns: - List[str]: A list of text lines after wrapping. + list[str]: Wrapped lines. """ if not text: return [""] + if not isinstance(text, str): + text = str(text) + if max_line_length is None: return text.splitlines() or [""] + if max_line_length <= 0: + raise ValueError(f"max_line_length must be a positive integer") + paragraphs = text.split("\n") - all_lines = [] + all_lines: list[str] = [] for paragraph in paragraphs: - if not paragraph: - # Keep empty lines + if paragraph == "": all_lines.append("") continue @@ -186,12 +193,9 @@ def wrap_text(text: str, max_line_length=None) -> list[str]: drop_whitespace=True, ) - if wrapped: - all_lines.extend(wrapped) - else: - all_lines.append("") + all_lines.extend(wrapped or [""]) - return all_lines if all_lines else [""] + return all_lines or [""] def validate_labels(labels: list[str] | None, detections: Detections): diff --git a/test/annotators/test_utils.py b/test/annotators/test_utils.py index d6abc381..9b19874a 100644 --- a/test/annotators/test_utils.py +++ b/test/annotators/test_utils.py @@ -5,7 +5,7 @@ from contextlib import ExitStack as DoesNotRaise import numpy as np import pytest -from supervision.annotators.utils import ColorLookup, resolve_color_idx +from supervision.annotators.utils import ColorLookup, resolve_color_idx, wrap_text from supervision.detection.core import Detections from test.test_utils import mock_detections @@ -108,3 +108,117 @@ def test_resolve_color_idx( color_lookup=color_lookup, ) assert result == expected_result + + +@pytest.mark.parametrize( + "text, max_line_length, expected_result, exception", + [ + ( + None, + None, + [""], + DoesNotRaise() + ), # text is None + ( + "", + None, + [""], + DoesNotRaise() + ), # empty string + ( + " \t ", + 3, + [""], + DoesNotRaise() + ), # whitespace-only (spaces + tab) + + ( + 12345, + None, + ["12345"], + DoesNotRaise() + ), # plain integer + ( + -6789, + None, + ["-6789"], + DoesNotRaise() + ), # negative integer + ( + np.int64(1000), + None, + ["1000"], + DoesNotRaise() + ), # NumPy int64 + ( + [1, 2, 3], + None, + ["[1, 2, 3]"], + DoesNotRaise() + ), # list to string + + ( + "When you play the game of thrones, you win or you die.\nFear cuts deeper than swords.\nA mind needs books as a sword needs a whetstone.", # noqa: E501 + None, + [ + "When you play the game of thrones, you win or you die.", + "Fear cuts deeper than swords.", + "A mind needs books as a sword needs a whetstone.", + ], + DoesNotRaise() + ), # Game-of-Thrones quotes, multiline + ( + "\n", + None, + [""], + DoesNotRaise() + ), # single newline + + ( + "valarmorghulisvalardoharis", + 6, + ["valarm", "orghul", "isvala", "rdohar", "is"], + DoesNotRaise() + ), # long Valyrian phrase, wrapped + ( + "Winter is coming\nFire and blood", + 10, + [ + "Winter is", + "coming", + "Fire and", + "blood", + ], + DoesNotRaise() + ), # mix of short/long with newline + + ( + "What is dead may never die", + 0, + None, + pytest.raises(ValueError) + ), # width 0 – invalid + ( + "A Lannister always pays his debts", + -1, + None, + pytest.raises(ValueError) + ), # width -1 – invalid + + ( + None, + 10, + [""], + DoesNotRaise() + ), # text None, width set + ], +) +def test_wrap_text( + text: object, + max_line_length: int | None, + expected_result: list[str], + exception: Exception, +) -> None: + with exception: + result = wrap_text(text=text, max_line_length=max_line_length) + assert result == expected_result \ No newline at end of file From 13ab215fdfda808985e0c6d83a814f9d790126a4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:05:41 +0000 Subject: [PATCH 2/4] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto=20?= =?UTF-8?q?format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/annotators/utils.py | 2 +- test/annotators/test_utils.py | 82 +++++++-------------------------- 2 files changed, 17 insertions(+), 67 deletions(-) diff --git a/supervision/annotators/utils.py b/supervision/annotators/utils.py index f3f38fc3..42a436c9 100644 --- a/supervision/annotators/utils.py +++ b/supervision/annotators/utils.py @@ -175,7 +175,7 @@ def wrap_text(text: Any, max_line_length=None) -> list[str]: return text.splitlines() or [""] if max_line_length <= 0: - raise ValueError(f"max_line_length must be a positive integer") + raise ValueError("max_line_length must be a positive integer") paragraphs = text.split("\n") all_lines: list[str] = [] diff --git a/test/annotators/test_utils.py b/test/annotators/test_utils.py index 9b19874a..89ae81bb 100644 --- a/test/annotators/test_utils.py +++ b/test/annotators/test_utils.py @@ -113,72 +113,29 @@ def test_resolve_color_idx( @pytest.mark.parametrize( "text, max_line_length, expected_result, exception", [ + (None, None, [""], DoesNotRaise()), # text is None + ("", None, [""], DoesNotRaise()), # empty string + (" \t ", 3, [""], DoesNotRaise()), # whitespace-only (spaces + tab) + (12345, None, ["12345"], DoesNotRaise()), # plain integer + (-6789, None, ["-6789"], DoesNotRaise()), # negative integer + (np.int64(1000), None, ["1000"], DoesNotRaise()), # NumPy int64 + ([1, 2, 3], None, ["[1, 2, 3]"], DoesNotRaise()), # list to string ( - None, - None, - [""], - DoesNotRaise() - ), # text is None - ( - "", - None, - [""], - DoesNotRaise() - ), # empty string - ( - " \t ", - 3, - [""], - DoesNotRaise() - ), # whitespace-only (spaces + tab) - - ( - 12345, - None, - ["12345"], - DoesNotRaise() - ), # plain integer - ( - -6789, - None, - ["-6789"], - DoesNotRaise() - ), # negative integer - ( - np.int64(1000), - None, - ["1000"], - DoesNotRaise() - ), # NumPy int64 - ( - [1, 2, 3], - None, - ["[1, 2, 3]"], - DoesNotRaise() - ), # list to string - - ( - "When you play the game of thrones, you win or you die.\nFear cuts deeper than swords.\nA mind needs books as a sword needs a whetstone.", # noqa: E501 + "When you play the game of thrones, you win or you die.\nFear cuts deeper than swords.\nA mind needs books as a sword needs a whetstone.", # noqa: E501 None, [ "When you play the game of thrones, you win or you die.", "Fear cuts deeper than swords.", "A mind needs books as a sword needs a whetstone.", ], - DoesNotRaise() + DoesNotRaise(), ), # Game-of-Thrones quotes, multiline - ( - "\n", - None, - [""], - DoesNotRaise() - ), # single newline - + ("\n", None, [""], DoesNotRaise()), # single newline ( "valarmorghulisvalardoharis", 6, ["valarm", "orghul", "isvala", "rdohar", "is"], - DoesNotRaise() + DoesNotRaise(), ), # long Valyrian phrase, wrapped ( "Winter is coming\nFire and blood", @@ -189,28 +146,21 @@ def test_resolve_color_idx( "Fire and", "blood", ], - DoesNotRaise() + DoesNotRaise(), ), # mix of short/long with newline - ( "What is dead may never die", 0, None, - pytest.raises(ValueError) + pytest.raises(ValueError), ), # width 0 – invalid ( "A Lannister always pays his debts", -1, None, - pytest.raises(ValueError) + pytest.raises(ValueError), ), # width -1 – invalid - - ( - None, - 10, - [""], - DoesNotRaise() - ), # text None, width set + (None, 10, [""], DoesNotRaise()), # text None, width set ], ) def test_wrap_text( @@ -221,4 +171,4 @@ def test_wrap_text( ) -> None: with exception: result = wrap_text(text=text, max_line_length=max_line_length) - assert result == expected_result \ No newline at end of file + assert result == expected_result From aab3160cd73b42107dcd12a3219238f4a105f0c0 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 6 Aug 2025 12:17:04 +0200 Subject: [PATCH 3/4] make ruff happy --- test/annotators/test_utils.py | 53 +++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/test/annotators/test_utils.py b/test/annotators/test_utils.py index 89ae81bb..32e3ec2a 100644 --- a/test/annotators/test_utils.py +++ b/test/annotators/test_utils.py @@ -113,13 +113,48 @@ def test_resolve_color_idx( @pytest.mark.parametrize( "text, max_line_length, expected_result, exception", [ - (None, None, [""], DoesNotRaise()), # text is None - ("", None, [""], DoesNotRaise()), # empty string - (" \t ", 3, [""], DoesNotRaise()), # whitespace-only (spaces + tab) - (12345, None, ["12345"], DoesNotRaise()), # plain integer - (-6789, None, ["-6789"], DoesNotRaise()), # negative integer - (np.int64(1000), None, ["1000"], DoesNotRaise()), # NumPy int64 - ([1, 2, 3], None, ["[1, 2, 3]"], DoesNotRaise()), # list to string + ( + None, + None, + [""], + DoesNotRaise() + ), # text is None + ( + "", + None, + [""], + DoesNotRaise() + ), # empty string + ( + " \t ", + 3, + [""], + DoesNotRaise() + ), # whitespace-only (spaces + tab) + ( + 12345, + None, + ["12345"], + DoesNotRaise() + ), # plain integer + ( + -6789, + None, + ["-6789"], + DoesNotRaise() + ), # negative integer + ( + np.int64(1000), + None, + ["1000"], + DoesNotRaise() + ), # NumPy int64 + ( + [1, 2, 3], + None, + ["[1, 2, 3]"], + DoesNotRaise() + ), # list to string ( "When you play the game of thrones, you win or you die.\nFear cuts deeper than swords.\nA mind needs books as a sword needs a whetstone.", # noqa: E501 None, @@ -153,13 +188,13 @@ def test_resolve_color_idx( 0, None, pytest.raises(ValueError), - ), # width 0 – invalid + ), # width 0 - invalid ( "A Lannister always pays his debts", -1, None, pytest.raises(ValueError), - ), # width -1 – invalid + ), # width -1 - invalid (None, 10, [""], DoesNotRaise()), # text None, width set ], ) From 46e59656809964479d83dd35b917754aa84395e0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:17:30 +0000 Subject: [PATCH 4/4] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto=20?= =?UTF-8?q?format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/annotators/test_utils.py | 49 +++++------------------------------ 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/test/annotators/test_utils.py b/test/annotators/test_utils.py index 32e3ec2a..3ab0f9b9 100644 --- a/test/annotators/test_utils.py +++ b/test/annotators/test_utils.py @@ -113,48 +113,13 @@ def test_resolve_color_idx( @pytest.mark.parametrize( "text, max_line_length, expected_result, exception", [ - ( - None, - None, - [""], - DoesNotRaise() - ), # text is None - ( - "", - None, - [""], - DoesNotRaise() - ), # empty string - ( - " \t ", - 3, - [""], - DoesNotRaise() - ), # whitespace-only (spaces + tab) - ( - 12345, - None, - ["12345"], - DoesNotRaise() - ), # plain integer - ( - -6789, - None, - ["-6789"], - DoesNotRaise() - ), # negative integer - ( - np.int64(1000), - None, - ["1000"], - DoesNotRaise() - ), # NumPy int64 - ( - [1, 2, 3], - None, - ["[1, 2, 3]"], - DoesNotRaise() - ), # list to string + (None, None, [""], DoesNotRaise()), # text is None + ("", None, [""], DoesNotRaise()), # empty string + (" \t ", 3, [""], DoesNotRaise()), # whitespace-only (spaces + tab) + (12345, None, ["12345"], DoesNotRaise()), # plain integer + (-6789, None, ["-6789"], DoesNotRaise()), # negative integer + (np.int64(1000), None, ["1000"], DoesNotRaise()), # NumPy int64 + ([1, 2, 3], None, ["[1, 2, 3]"], DoesNotRaise()), # list to string ( "When you play the game of thrones, you win or you die.\nFear cuts deeper than swords.\nA mind needs books as a sword needs a whetstone.", # noqa: E501 None,