Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b17cc2c

Browse files
committed
Merge pull request #5975 from tacaswell/enh_kwarg_normalize
ENH: add kwarg normalization function to cbook
1 parent 2ac494f commit b17cc2c

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

lib/matplotlib/cbook.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2544,6 +2544,114 @@ def safe_first_element(obj):
25442544
return next(iter(obj))
25452545

25462546

2547+
def normalize_kwargs(kw, alias_mapping=None, required=(), forbidden=(),
2548+
allowed=None):
2549+
"""Helper function to normalize kwarg inputs
2550+
2551+
The order they are resolved are:
2552+
2553+
1. aliasing
2554+
2. required
2555+
3. forbidden
2556+
4. allowed
2557+
2558+
This order means that only the canonical names need appear in
2559+
`allowed`, `forbidden`, `required`
2560+
2561+
Parameters
2562+
----------
2563+
2564+
alias_mapping, dict, optional
2565+
A mapping between a canonical name to a list of
2566+
aliases, in order of precedence from lowest to highest.
2567+
2568+
If the canonical value is not in the list it is assumed to have
2569+
the highest priority.
2570+
2571+
required : iterable, optional
2572+
A tuple of fields that must be in kwargs.
2573+
2574+
forbidden : iterable, optional
2575+
A list of keys which may not be in kwargs
2576+
2577+
allowed : tuple, optional
2578+
A tuple of allowed fields. If this not None, then raise if
2579+
`kw` contains any keys not in the union of `required`
2580+
and `allowed`. To allow only the required fields pass in
2581+
``()`` for `allowed`
2582+
2583+
Raises
2584+
------
2585+
TypeError
2586+
To match what python raises if invalid args/kwargs are passed to
2587+
a callable.
2588+
2589+
"""
2590+
# deal with default value of alias_mapping
2591+
if alias_mapping is None:
2592+
alias_mapping = dict()
2593+
2594+
# make a local so we can pop
2595+
kw = dict(kw)
2596+
# output dictionary
2597+
ret = dict()
2598+
2599+
# hit all alias mappings
2600+
for canonical, alias_list in six.iteritems(alias_mapping):
2601+
2602+
# the alias lists are ordered from lowest to highest priority
2603+
# so we know to use the last value in this list
2604+
tmp = []
2605+
seen = []
2606+
for a in alias_list:
2607+
try:
2608+
tmp.append(kw.pop(a))
2609+
seen.append(a)
2610+
except KeyError:
2611+
pass
2612+
# if canonical is not in the alias_list assume highest priority
2613+
if canonical not in alias_list:
2614+
try:
2615+
tmp.append(kw.pop(canonical))
2616+
seen.append(canonical)
2617+
except KeyError:
2618+
pass
2619+
# if we found anything in this set of aliases put it in the return
2620+
# dict
2621+
if tmp:
2622+
ret[canonical] = tmp[-1]
2623+
if len(tmp) > 1:
2624+
warnings.warn("Saw kwargs {seen!r} which are all aliases for "
2625+
"{canon!r}. Kept value from {used!r}".format(
2626+
seen=seen, canon=canonical, used=seen[-1]))
2627+
2628+
# at this point we know that all keys which are aliased are removed, update
2629+
# the return dictionary from the cleaned local copy of the input
2630+
ret.update(kw)
2631+
2632+
fail_keys = [k for k in required if k not in ret]
2633+
if fail_keys:
2634+
raise TypeError("The required keys {!r} "
2635+
"are not in kwargs".format(fail_keys))
2636+
2637+
fail_keys = [k for k in forbidden if k in ret]
2638+
if fail_keys:
2639+
raise TypeError("The forbidden keys {!r} "
2640+
"are in kwargs".format(fail_keys))
2641+
2642+
if allowed is not None:
2643+
allowed_set = set(required) | set(allowed)
2644+
fail_keys = [k for k in ret if k not in allowed_set]
2645+
if fail_keys:
2646+
raise TypeError("kwargs contains {keys!r} which are not in "
2647+
"the required {req!r} or "
2648+
"allowed {allow!r} keys".format(
2649+
keys=fail_keys, req=required,
2650+
allow=allowed))
2651+
2652+
return ret
2653+
2654+
25472655
def get_label(y, default_name):
25482656
try:
25492657
return y.name

lib/matplotlib/tests/test_cbook.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
unicode_literals)
33
import itertools
44
from weakref import ref
5+
import warnings
56

67
from matplotlib.externals import six
78

@@ -309,6 +310,67 @@ def dummy(self):
309310
pass
310311

311312

313+
def _kwarg_norm_helper(inp, expected, kwargs_to_norm, warn_count=0):
314+
315+
with warnings.catch_warnings(record=True) as w:
316+
warnings.simplefilter("always")
317+
assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm)
318+
assert len(w) == warn_count
319+
320+
321+
def _kwarg_norm_fail_helper(inp, kwargs_to_norm):
322+
assert_raises(TypeError, cbook.normalize_kwargs, inp, **kwargs_to_norm)
323+
324+
325+
def test_normalize_kwargs():
326+
fail_mapping = (
327+
({'a': 1}, {'forbidden': ('a')}),
328+
({'a': 1}, {'required': ('b')}),
329+
({'a': 1, 'b': 2}, {'required': ('a'), 'allowed': ()})
330+
)
331+
332+
for inp, kwargs in fail_mapping:
333+
yield _kwarg_norm_fail_helper, inp, kwargs
334+
335+
warn_passing_mapping = (
336+
({'a': 1, 'b': 2}, {'a': 1}, {'alias_mapping': {'a': ['b']}}, 1),
337+
({'a': 1, 'b': 2}, {'a': 1}, {'alias_mapping': {'a': ['b']},
338+
'allowed': ('a',)}, 1),
339+
({'a': 1, 'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}, 1),
340+
341+
({'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'c': 3},
342+
{'alias_mapping': {'a': ['b']}, 'required': ('a', )}, 1),
343+
344+
)
345+
346+
for inp, exp, kwargs, wc in warn_passing_mapping:
347+
yield _kwarg_norm_helper, inp, exp, kwargs, wc
348+
349+
pass_mapping = (
350+
({'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {}),
351+
({'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}),
352+
({'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['b']},
353+
'forbidden': ('b', )}),
354+
355+
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'required': ('a', ),
356+
'allowed': ('c', )}),
357+
358+
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'required': ('a', 'c'),
359+
'allowed': ('c', )}),
360+
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'required': ('a', 'c'),
361+
'allowed': ('a', 'c')}),
362+
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'required': ('a', 'c'),
363+
'allowed': ()}),
364+
365+
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'required': ('a', 'c')}),
366+
({'a': 1, 'c': 3}, {'a': 1, 'c': 3}, {'allowed': ('a', 'c')}),
367+
368+
)
369+
370+
for inp, exp, kwargs in pass_mapping:
371+
yield _kwarg_norm_helper, inp, exp, kwargs
372+
373+
312374
def test_to_prestep():
313375
x = np.arange(4)
314376
y1 = np.arange(4)

0 commit comments

Comments
 (0)