Skip to content

Commit

Permalink
Rename Switch to FilterSwitch.
Browse files Browse the repository at this point in the history
Keeps naming convention consistent with other complex filters.
  • Loading branch information
todofixthis committed Sep 30, 2019
1 parent 3f1e990 commit 089b85d
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 74 deletions.
50 changes: 27 additions & 23 deletions docs/filters_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,10 @@ These filters are covered in more detail in :doc:`/complex_filters`.
``FilterRepeater`` can also process mappings (e.g., ``dict``); it will apply
the filters to every value in the mapping, preserving the keys.

:py:class:`filters.Switch`
:py:class:`filters.FilterSwitch`
Conditionally invokes a filter based on the output of a function.

``Switch`` takes 2-3 parameters:
``FilterSwitch`` takes 2-3 parameters:

- ``getter: Callable[[Any], Hashable]`` - a function that extracts the
comparison value from the incoming value. Whatever this function returns
Expand All @@ -336,32 +336,36 @@ These filters are covered in more detail in :doc:`/complex_filters`.
specified, then the incoming value will be considered invalid if the
comparison value doesn't match any cases.

Example of a ``Switch`` that selects the correct filter to use based upon the
incoming value's ``name`` value:
Example of a ``FilterSwitch`` that selects the correct filter to use based
upon the incoming value's ``name`` value:

.. code-block:: py
runner = f.FilterRunner(
f.Switch(
# This function will extract the comparison value.
getter=lambda value: value['name'],
# These are the cases that the comparison value might
# match.
cases={
'price': f.FilterMapper({'value': f.Int | f.Min(0)}),
'color': f.FilterMapper({'value': f.Choice({'r', 'g', 'b'})}),
# etc.
},
# This is the filter that will be used if none of the cases match.
default=f.FilterMapper({'value': f.Unicode}),
),
# Example value.
{'name': price, 'value': 42},
switch = f.FilterSwitch(
# This function will extract the comparison value.
getter=lambda value: value['name'],
# These are the cases that the comparison value might
# match.
cases={
'price': f.FilterMapper({'value': f.Int | f.Min(0)}),
'color': f.FilterMapper({'value': f.Choice({'r', 'g', 'b'})}),
# etc.
},
# This is the filter that will be used if none of the cases match.
default=f.FilterMapper({'value': f.Unicode}),
)
# Applies the 'price' filter:
switch.apply({'name': price, 'value': 42})
# Applies the 'color' filter:
switch.apply({'name': color, 'value': 'b'})
# Applies the default filter:
switch.apply({'name': 'mfg', 'value': 'Acme Widget Co.'})
Extensions
==========
The following filters are provided by the
Expand Down
97 changes: 49 additions & 48 deletions filters/complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
__all__ = [
'FilterMapper',
'FilterRepeater',
'FilterSwitch',
'NamedTuple',
'Switch',
]


Expand Down Expand Up @@ -391,6 +391,54 @@ def unicodify_key(key: typing.Any) -> str:
return repr(key)


class FilterSwitch(BaseFilter):
"""
Chooses the next filter to apply based on the output of a callable.
"""

def __init__(
self,
getter: typing.Callable[[typing.Any], typing.Hashable],
cases: typing.Mapping[typing.Hashable, FilterCompatible],
default: typing.Optional[FilterCompatible] = None,
) -> None:
"""
:param getter:
Callable used to extract the value to match against switch
cases.
:param cases:
Mapping of possible values to the corresponding filters.
:param default:
Default filter to use, if none of the cases are matched.
If null (default) then the value will be considered invalid
if it doesn't match any cases.
"""
super().__init__()

self.getter = getter
self.cases = cases
self.default = default

def _apply(self, value):
gotten = self.getter(value) # type: typing.Hashable

if not self.default:
gotten = self._filter(gotten, Choice(self.cases.keys()))

if self._has_errors:
return None

if gotten in self.cases:
return self._filter(value, self.cases[gotten])

# If we get here, then we have set a default filter.
return self._filter(value, self.default)



class NamedTuple(BaseFilter):
"""
Attempts to convert the incoming value into a namedtuple.
Expand Down Expand Up @@ -481,50 +529,3 @@ def _apply(self, value):
return self.type(**filtered)
else:
return value


class Switch(BaseFilter):
"""
Chooses the next filter to apply based on the output of a callable.
"""

def __init__(
self,
getter: typing.Callable[[typing.Any], typing.Hashable],
cases: typing.Mapping[typing.Hashable, FilterCompatible],
default: typing.Optional[FilterCompatible] = None,
) -> None:
"""
:param getter:
Callable used to extract the value to match against switch
cases.
:param cases:
Mapping of possible values to the corresponding filters.
:param default:
Default filter to use, if none of the cases are matched.
If null (default) then the value will be considered invalid
if it doesn't match any cases.
"""
super().__init__()

self.getter = getter
self.cases = cases
self.default = default

def _apply(self, value):
gotten = self.getter(value) # type: typing.Hashable

if not self.default:
gotten = self._filter(gotten, Choice(self.cases.keys()))

if self._has_errors:
return None

if gotten in self.cases:
return self._filter(value, self.cases[gotten])

# If we get here, then we have set a default filter.
return self._filter(value, self.default)
1 change: 1 addition & 0 deletions filters/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class Call(BaseFilter):
But, in a pinch, this is a handy way to quickly integrate a custom
function into a filter chain.
"""

def __init__(self,
callable_: typing.Callable[..., typing.Any],
*extra_args,
Expand Down
6 changes: 3 additions & 3 deletions test/complex_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,14 +1194,14 @@ def test_fail_filter_map(self):
)


class SwitchTestCase(BaseFilterTestCase):
filter_type = f.Switch
class FilterSwitchTestCase(BaseFilterTestCase):
filter_type = f.FilterSwitch

def test_pass_none(self):
"""
``None`` always passes this filter.
Use ``f.Required | f.Switch`` to reject null values.
Use ``f.Required | f.FilterSwitch`` to reject null values.
"""
self.assertFilterPasses(
self._filter(
Expand Down

0 comments on commit 089b85d

Please sign in to comment.