Skip to content

Commit 5870731

Browse files
tdhopperjreback
authored andcommitted
BUG: Validate float_format setting as callable or None
The `float_format` setting takes either None or a callable object that returns a formatted string given a float value. Currently, the setting isn't validated, so cryptic error messages are returned if, for example, a format non-callable format string is given (see \pandas-dev#12704). Add a standard validation method and use it to validate the `float_format` setting. closes pandas-dev#12706 closes pandas-dev#12711
1 parent 4e7974a commit 5870731

File tree

4 files changed

+42
-6
lines changed

4 files changed

+42
-6
lines changed

doc/source/whatsnew/v0.18.1.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ Bug Fixes
124124
- Bug in numpy compatibility of ``np.round()`` on a ``Series`` (:issue:`12600`)
125125
- Bug in ``Series`` construction with ``Categorical`` and ``dtype='category'`` is specified (:issue:`12574`)
126126
- Bugs in concatenation with a coercable dtype was too aggressive. (:issue:`12411`, :issue:`12045`, :issue:`11594`, :issue:`10571`)
127+
- Bug in ``float_format`` option with option not being validated as a callable. (:issue:`12706`)
127128

128129

129130

@@ -167,4 +168,4 @@ Bug Fixes
167168

168169

169170
- Bug in ``pivot_table`` when ``margins=True`` and ``dropna=True`` where nulls still contributed to margin count (:issue:`12577`)
170-
- Bug in ``Series.name`` when ``name`` attribute can be a hashable type (:issue:`12610`)
171+
- Bug in ``Series.name`` when ``name`` attribute can be a hashable type (:issue:`12610`)

pandas/core/config.py

+29-3
Original file line numberDiff line numberDiff line change
@@ -786,12 +786,20 @@ def inner(x):
786786

787787

788788
def is_one_of_factory(legal_values):
789+
790+
callables = [c for c in legal_values if callable(c)]
791+
legal_values = [c for c in legal_values if not callable(c)]
792+
789793
def inner(x):
790794
from pandas.core.common import pprint_thing as pp
791795
if x not in legal_values:
792-
pp_values = lmap(pp, legal_values)
793-
raise ValueError("Value must be one of %s" %
794-
pp("|".join(pp_values)))
796+
797+
if not any([c(x) for c in callables]):
798+
pp_values = pp("|".join(lmap(pp, legal_values)))
799+
msg = "Value must be one of {0}".format(pp_values)
800+
if len(callables):
801+
msg += " or a callable"
802+
raise ValueError(msg)
795803

796804
return inner
797805

@@ -803,3 +811,21 @@ def inner(x):
803811
is_str = is_type_factory(str)
804812
is_unicode = is_type_factory(compat.text_type)
805813
is_text = is_instance_factory((str, bytes))
814+
815+
816+
def is_callable(obj):
817+
"""
818+
819+
Parameters
820+
----------
821+
`obj` - the object to be checked
822+
823+
Returns
824+
-------
825+
validator - returns True if object is callable
826+
raises ValueError otherwise.
827+
828+
"""
829+
if not callable(obj):
830+
raise ValueError("Value must be a callable")
831+
return True

pandas/core/config_init.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
import pandas.core.config as cf
1515
from pandas.core.config import (is_int, is_bool, is_text, is_instance_factory,
16-
is_one_of_factory, get_default_val)
16+
is_one_of_factory, get_default_val,
17+
is_callable)
1718
from pandas.core.format import detect_console_encoding
1819

1920
#
@@ -279,7 +280,8 @@ def mpl_style_cb(key):
279280

280281
with cf.config_prefix('display'):
281282
cf.register_option('precision', 6, pc_precision_doc, validator=is_int)
282-
cf.register_option('float_format', None, float_format_doc)
283+
cf.register_option('float_format', None, float_format_doc,
284+
validator=is_one_of_factory([None, is_callable]))
283285
cf.register_option('column_space', 12, validator=is_int)
284286
cf.register_option('max_info_rows', 1690785, pc_max_info_rows_doc,
285287
validator=is_instance_factory((int, type(None))))

pandas/tests/test_config.py

+7
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ def test_validation(self):
206206
self.assertRaises(ValueError, self.cf.set_option, 'a', 'ab')
207207
self.assertRaises(ValueError, self.cf.set_option, 'b.c', 1)
208208

209+
validator = self.cf.is_one_of_factory([None, self.cf.is_callable])
210+
self.cf.register_option('b', lambda: None, 'doc',
211+
validator=validator)
212+
self.cf.set_option('b', '%.1f'.format) # Formatter is callable
213+
self.cf.set_option('b', None) # Formatter is none (default)
214+
self.assertRaises(ValueError, self.cf.set_option, 'b', '%.1f')
215+
209216
def test_reset_option(self):
210217
self.cf.register_option('a', 1, 'doc', validator=self.cf.is_int)
211218
self.cf.register_option('b.c', 'hullo', 'doc2',

0 commit comments

Comments
 (0)