Skip to content

Commit 168eda8

Browse files
committed
Add validation for formatter parameters and deprecate repr_rows alias
1 parent 0563f6c commit 168eda8

File tree

2 files changed

+176
-39
lines changed

2 files changed

+176
-39
lines changed

python/datafusion/dataframe_formatter.py

Lines changed: 154 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from __future__ import annotations
2020

21+
import warnings
2122
from typing import (
2223
TYPE_CHECKING,
2324
Any,
@@ -61,6 +62,93 @@ def _validate_bool(value: Any, param_name: str) -> None:
6162
raise TypeError(msg)
6263

6364

65+
def _validate_formatter_parameters(
66+
max_cell_length: int,
67+
max_width: int,
68+
max_height: int,
69+
max_memory_bytes: int,
70+
min_rows_display: int,
71+
max_rows: int | None,
72+
repr_rows: int | None,
73+
enable_cell_expansion: bool,
74+
show_truncation_message: bool,
75+
use_shared_styles: bool,
76+
custom_css: str | None,
77+
style_provider: Any,
78+
) -> int:
79+
"""Validate all formatter parameters and return resolved max_rows value.
80+
81+
Args:
82+
max_cell_length: Maximum cell length value to validate
83+
max_width: Maximum width value to validate
84+
max_height: Maximum height value to validate
85+
max_memory_bytes: Maximum memory bytes value to validate
86+
min_rows_display: Minimum rows to display value to validate
87+
max_rows: Maximum rows value to validate (None means use default)
88+
repr_rows: Deprecated repr_rows value to validate
89+
enable_cell_expansion: Boolean expansion flag to validate
90+
show_truncation_message: Boolean message flag to validate
91+
use_shared_styles: Boolean styles flag to validate
92+
custom_css: Custom CSS string to validate
93+
style_provider: Style provider object to validate
94+
95+
Returns:
96+
The resolved max_rows value after handling repr_rows deprecation
97+
98+
Raises:
99+
ValueError: If any numeric parameter is invalid or constraints are violated
100+
TypeError: If any parameter has invalid type
101+
DeprecationWarning: If repr_rows parameter is used
102+
"""
103+
# Validate numeric parameters
104+
_validate_positive_int(max_cell_length, "max_cell_length")
105+
_validate_positive_int(max_width, "max_width")
106+
_validate_positive_int(max_height, "max_height")
107+
_validate_positive_int(max_memory_bytes, "max_memory_bytes")
108+
_validate_positive_int(min_rows_display, "min_rows_display")
109+
110+
# Handle deprecated repr_rows parameter
111+
if repr_rows is not None:
112+
warnings.warn(
113+
"repr_rows parameter is deprecated, use max_rows instead",
114+
DeprecationWarning,
115+
stacklevel=4,
116+
)
117+
_validate_positive_int(repr_rows, "repr_rows")
118+
if max_rows is not None and repr_rows != max_rows:
119+
msg = "Cannot specify both repr_rows and max_rows; use max_rows only"
120+
raise ValueError(msg)
121+
max_rows = repr_rows
122+
123+
# Use default if max_rows was not provided
124+
if max_rows is None:
125+
max_rows = 10
126+
127+
_validate_positive_int(max_rows, "max_rows")
128+
129+
# Validate constraint: min_rows_display <= max_rows
130+
if min_rows_display > max_rows:
131+
msg = "min_rows_display must be less than or equal to max_rows"
132+
raise ValueError(msg)
133+
134+
# Validate boolean parameters
135+
_validate_bool(enable_cell_expansion, "enable_cell_expansion")
136+
_validate_bool(show_truncation_message, "show_truncation_message")
137+
_validate_bool(use_shared_styles, "use_shared_styles")
138+
139+
# Validate custom_css
140+
if custom_css is not None and not isinstance(custom_css, str):
141+
msg = "custom_css must be None or a string"
142+
raise TypeError(msg)
143+
144+
# Validate style_provider
145+
if style_provider is not None and not isinstance(style_provider, StyleProvider):
146+
msg = "style_provider must implement the StyleProvider protocol"
147+
raise TypeError(msg)
148+
149+
return max_rows
150+
151+
64152
@runtime_checkable
65153
class CellFormatter(Protocol):
66154
"""Protocol for cell value formatters."""
@@ -145,7 +233,7 @@ def __init__(
145233
max_height: int = 300,
146234
max_memory_bytes: int = 2 * 1024 * 1024, # 2 MB
147235
min_rows_display: int = 10,
148-
max_rows: int = 10,
236+
max_rows: int | None = None,
149237
repr_rows: int | None = None,
150238
enable_cell_expansion: bool = True,
151239
custom_css: str | None = None,
@@ -196,50 +284,28 @@ def __init__(
196284
or if style_provider is provided but does not implement the StyleProvider
197285
protocol.
198286
"""
199-
# Validate numeric parameters
200-
_validate_positive_int(max_cell_length, "max_cell_length")
201-
_validate_positive_int(max_width, "max_width")
202-
_validate_positive_int(max_height, "max_height")
203-
_validate_positive_int(max_memory_bytes, "max_memory_bytes")
204-
_validate_positive_int(min_rows_display, "min_rows_display")
205-
206-
if repr_rows is not None and repr_rows != max_rows:
207-
msg = "Specify only max_rows (repr_rows is deprecated)"
208-
raise ValueError(msg)
209-
210-
if repr_rows is not None:
211-
_validate_positive_int(repr_rows, "repr_rows")
212-
max_rows = repr_rows
213-
214-
_validate_positive_int(max_rows, "max_rows")
215-
216-
if min_rows_display > max_rows:
217-
msg = "min_rows_display must be less than or equal to max_rows"
218-
raise ValueError(msg)
219-
220-
# Validate boolean parameters
221-
_validate_bool(enable_cell_expansion, "enable_cell_expansion")
222-
_validate_bool(show_truncation_message, "show_truncation_message")
223-
_validate_bool(use_shared_styles, "use_shared_styles")
224-
225-
# Validate custom_css
226-
if custom_css is not None and not isinstance(custom_css, str):
227-
msg = "custom_css must be None or a string"
228-
raise TypeError(msg)
229-
230-
# Validate style_provider
231-
if style_provider is not None and not isinstance(style_provider, StyleProvider):
232-
msg = "style_provider must implement the StyleProvider protocol"
233-
raise TypeError(msg)
287+
# Validate all parameters and get resolved max_rows
288+
resolved_max_rows = _validate_formatter_parameters(
289+
max_cell_length,
290+
max_width,
291+
max_height,
292+
max_memory_bytes,
293+
min_rows_display,
294+
max_rows,
295+
repr_rows,
296+
enable_cell_expansion,
297+
show_truncation_message,
298+
use_shared_styles,
299+
custom_css,
300+
style_provider,
301+
)
234302

235303
self.max_cell_length = max_cell_length
236304
self.max_width = max_width
237305
self.max_height = max_height
238306
self.max_memory_bytes = max_memory_bytes
239307
self.min_rows_display = min_rows_display
240-
self.max_rows = max_rows
241-
# Backwards-compatible alias
242-
self.repr_rows = max_rows
308+
self._max_rows = resolved_max_rows
243309
self.enable_cell_expansion = enable_cell_expansion
244310
self.custom_css = custom_css
245311
self.show_truncation_message = show_truncation_message
@@ -251,6 +317,55 @@ def __init__(
251317
self._custom_cell_builder: Callable[[Any, int, int, str], str] | None = None
252318
self._custom_header_builder: Callable[[Any], str] | None = None
253319

320+
@property
321+
def max_rows(self) -> int:
322+
"""Get the maximum number of rows to display.
323+
324+
Returns:
325+
The maximum number of rows to display in repr output
326+
"""
327+
return self._max_rows
328+
329+
@max_rows.setter
330+
def max_rows(self, value: int) -> None:
331+
"""Set the maximum number of rows to display.
332+
333+
Args:
334+
value: The maximum number of rows
335+
"""
336+
self._max_rows = value
337+
338+
@property
339+
def repr_rows(self) -> int:
340+
"""Get the maximum number of rows (deprecated name).
341+
342+
.. deprecated::
343+
Use :attr:`max_rows` instead. This property is provided for
344+
backward compatibility.
345+
346+
Returns:
347+
The maximum number of rows to display
348+
"""
349+
return self._max_rows
350+
351+
@repr_rows.setter
352+
def repr_rows(self, value: int) -> None:
353+
"""Set the maximum number of rows using deprecated name.
354+
355+
.. deprecated::
356+
Use :attr:`max_rows` setter instead. This property is provided for
357+
backward compatibility.
358+
359+
Args:
360+
value: The maximum number of rows
361+
"""
362+
warnings.warn(
363+
"repr_rows is deprecated, use max_rows instead",
364+
DeprecationWarning,
365+
stacklevel=2,
366+
)
367+
self._max_rows = value
368+
254369
def register_formatter(self, type_class: type, formatter: CellFormatter) -> None:
255370
"""Register a custom formatter for a specific data type.
256371

python/tests/test_dataframe.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,28 @@ def test_html_formatter_validation():
15101510
DataFrameHtmlFormatter(min_rows_display=5, max_rows=4)
15111511

15121512

1513+
def test_repr_rows_backward_compatibility(clean_formatter_state):
1514+
"""Test that repr_rows parameter still works as deprecated alias."""
1515+
# Should work when not conflicting with max_rows
1516+
with pytest.warns(DeprecationWarning, match="repr_rows parameter is deprecated"):
1517+
formatter = DataFrameHtmlFormatter(repr_rows=15, min_rows_display=10)
1518+
assert formatter.max_rows == 15
1519+
assert formatter.repr_rows == 15
1520+
1521+
# Should fail when conflicting with max_rows
1522+
with pytest.raises(
1523+
ValueError, match="Cannot specify both repr_rows and max_rows"
1524+
):
1525+
DataFrameHtmlFormatter(repr_rows=5, max_rows=10)
1526+
1527+
# Setting repr_rows via property should warn
1528+
formatter2 = DataFrameHtmlFormatter()
1529+
with pytest.warns(DeprecationWarning, match="repr_rows is deprecated"):
1530+
formatter2.repr_rows = 7
1531+
assert formatter2.max_rows == 7
1532+
assert formatter2.repr_rows == 7
1533+
1534+
15131535
def test_configure_formatter(df, clean_formatter_state):
15141536
"""Test using custom style providers with the HTML formatter and configured
15151537
parameters."""

0 commit comments

Comments
 (0)