Skip to content

Commit ade2668

Browse files
authored
Merge pull request #1932 from roboflow/fix/numpy.int64-object-has-no-attribute-splitlines
fix: AttributeError: 'numpy.int64' object has no attribute 'splitlines'
2 parents e139369 + 46e5965 commit ade2668

File tree

2 files changed

+82
-14
lines changed

2 files changed

+82
-14
lines changed

supervision/annotators/utils.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import textwrap
44
from enum import Enum
5+
from typing import Any
56

67
import numpy as np
78

@@ -151,30 +152,36 @@ def resolve_color(
151152
return get_color_by_index(color=color, idx=idx)
152153

153154

154-
def wrap_text(text: str, max_line_length=None) -> list[str]:
155+
def wrap_text(text: Any, max_line_length=None) -> list[str]:
155156
"""
156-
Wraps text to the specified maximum line length, respecting existing newlines.
157-
Uses the textwrap library for robust text wrapping.
157+
Wrap `text` to the specified maximum line length, respecting existing
158+
newlines. Falls back to str() if `text` is not already a string.
158159
159160
Args:
160-
text (str): The text to wrap.
161+
text (Any): The text (or object) to wrap.
162+
max_line_length (int | None): Maximum width for each wrapped line.
161163
162164
Returns:
163-
List[str]: A list of text lines after wrapping.
165+
list[str]: Wrapped lines.
164166
"""
165167

166168
if not text:
167169
return [""]
168170

171+
if not isinstance(text, str):
172+
text = str(text)
173+
169174
if max_line_length is None:
170175
return text.splitlines() or [""]
171176

177+
if max_line_length <= 0:
178+
raise ValueError("max_line_length must be a positive integer")
179+
172180
paragraphs = text.split("\n")
173-
all_lines = []
181+
all_lines: list[str] = []
174182

175183
for paragraph in paragraphs:
176-
if not paragraph:
177-
# Keep empty lines
184+
if paragraph == "":
178185
all_lines.append("")
179186
continue
180187

@@ -186,12 +193,9 @@ def wrap_text(text: str, max_line_length=None) -> list[str]:
186193
drop_whitespace=True,
187194
)
188195

189-
if wrapped:
190-
all_lines.extend(wrapped)
191-
else:
192-
all_lines.append("")
196+
all_lines.extend(wrapped or [""])
193197

194-
return all_lines if all_lines else [""]
198+
return all_lines or [""]
195199

196200

197201
def validate_labels(labels: list[str] | None, detections: Detections):

test/annotators/test_utils.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import numpy as np
66
import pytest
77

8-
from supervision.annotators.utils import ColorLookup, resolve_color_idx
8+
from supervision.annotators.utils import ColorLookup, resolve_color_idx, wrap_text
99
from supervision.detection.core import Detections
1010
from test.test_utils import mock_detections
1111

@@ -108,3 +108,67 @@ def test_resolve_color_idx(
108108
color_lookup=color_lookup,
109109
)
110110
assert result == expected_result
111+
112+
113+
@pytest.mark.parametrize(
114+
"text, max_line_length, expected_result, exception",
115+
[
116+
(None, None, [""], DoesNotRaise()), # text is None
117+
("", None, [""], DoesNotRaise()), # empty string
118+
(" \t ", 3, [""], DoesNotRaise()), # whitespace-only (spaces + tab)
119+
(12345, None, ["12345"], DoesNotRaise()), # plain integer
120+
(-6789, None, ["-6789"], DoesNotRaise()), # negative integer
121+
(np.int64(1000), None, ["1000"], DoesNotRaise()), # NumPy int64
122+
([1, 2, 3], None, ["[1, 2, 3]"], DoesNotRaise()), # list to string
123+
(
124+
"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
125+
None,
126+
[
127+
"When you play the game of thrones, you win or you die.",
128+
"Fear cuts deeper than swords.",
129+
"A mind needs books as a sword needs a whetstone.",
130+
],
131+
DoesNotRaise(),
132+
), # Game-of-Thrones quotes, multiline
133+
("\n", None, [""], DoesNotRaise()), # single newline
134+
(
135+
"valarmorghulisvalardoharis",
136+
6,
137+
["valarm", "orghul", "isvala", "rdohar", "is"],
138+
DoesNotRaise(),
139+
), # long Valyrian phrase, wrapped
140+
(
141+
"Winter is coming\nFire and blood",
142+
10,
143+
[
144+
"Winter is",
145+
"coming",
146+
"Fire and",
147+
"blood",
148+
],
149+
DoesNotRaise(),
150+
), # mix of short/long with newline
151+
(
152+
"What is dead may never die",
153+
0,
154+
None,
155+
pytest.raises(ValueError),
156+
), # width 0 - invalid
157+
(
158+
"A Lannister always pays his debts",
159+
-1,
160+
None,
161+
pytest.raises(ValueError),
162+
), # width -1 - invalid
163+
(None, 10, [""], DoesNotRaise()), # text None, width set
164+
],
165+
)
166+
def test_wrap_text(
167+
text: object,
168+
max_line_length: int | None,
169+
expected_result: list[str],
170+
exception: Exception,
171+
) -> None:
172+
with exception:
173+
result = wrap_text(text=text, max_line_length=max_line_length)
174+
assert result == expected_result

0 commit comments

Comments
 (0)