示例#1
0
    def test_mapper_chained_with_mapper(self):
        """
        Chaining two FilterMappers together has basically the same
        effect as combining their Filters.

        Generally, combining two FilterMappers into a single instance
        is much easier to read/maintain than chaining them, but in
        a few cases it may be unavoidable (for example, if you need
        each FilterMapper to handle extra and/or missing keys
        differently).
        """
        fm1 = f.FilterMapper(
            {
                'id': f.Int | f.Min(1),
            },
            allow_missing_keys=True,
            allow_extra_keys=True,
        )

        fm2 = f.FilterMapper(
            {
                'id': f.Required | f.Max(256),
                'subject': f.NotEmpty | f.MaxLength(16),
            },
            allow_missing_keys=False,
            allow_extra_keys=False,
        )

        self.filter_type = lambda: fm1 | fm2

        self.assertFilterPasses(
            {
                'id': '42',
                'subject': 'Hello, world!',
            },
            {
                'id': 42,
                'subject': 'Hello, world!',
            },
        )

        self.assertFilterErrors(
            {},
            {
                # ``fm1`` allows missing keys, so it sets 'id' to
                # ``None``.
                # However, ``fm2`` does not allow ``None`` for 'id'
                # (because of the ``Required`` filter).
                'id': [f.Required.CODE_EMPTY],

                # `fm1` does not care about `subject`, but `fm2`
                # expects it to be there.
                'subject': [f.FilterMapper.CODE_MISSING_KEY],
            },
            expected_value={
                'id': None,
                'subject': None,
            },
        )
示例#2
0
    def test_pass_mapping(self):
        """
        A FilterRepeater is applied to a dict containing valid values.
        """
        self.filter_type = lambda: f.FilterMapper({
            'id':       f.Required | f.Int | f.Min(1),
            'subject':  f.NotEmpty | f.MaxLength(16),
        })

        filter_ = self._filter({
            'id':       '42',
            'subject':  'Hello, world!',
        })

        self.assertFilterPasses(
            filter_,

            {
                'id':       42,
                'subject':  'Hello, world!',
            },
        )

        # The result is a dict, to match the type of the filter map.
        self.assertIs(type(filter_.cleaned_data), dict)
示例#3
0
    def test_fail_mapping(self):
        """
        A FilterRepeater is applied to a dict containing invalid
        values.
        """
        self.filter_type = lambda: f.FilterMapper({
            'id':       f.Required | f.Int | f.Min(1),
            'subject':  f.NotEmpty | f.MaxLength(16),
        })

        self.assertFilterErrors(
            {
                'id':       None,
                'subject':  'Antidisestablishmentarianism',
            },

            {
                'id':       [f.Required.CODE_EMPTY],
                'subject':  [f.MaxLength.CODE_TOO_LONG],
            },

            expected_value = {
                'id':       None,
                'subject':  None,
            }
        )
示例#4
0
    def test_missing_keys_specified(self):
        """
        FilterMappers can be configured to allow some missing keys but
        not others.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id': f.Required | f.Int | f.Min(1),
                'subject': f.NotEmpty | f.MaxLength(16),
            },
            allow_missing_keys={'subject'},
        )

        # The FilterMapper is configured to treat missing 'subject' as
        # if it were set to `None`.
        self.assertFilterPasses(
            {'id': '42'},
            {
                'id': 42,
                'subject': None,
            },
        )

        # However, 'id' is still required.
        self.assertFilterErrors({
            'subject': 'Hello, world!',
        }, {
            'id': [f.FilterMapper.CODE_MISSING_KEY],
        },
                                expected_value={
                                    'id': None,
                                    'subject': 'Hello, world!',
                                })
示例#5
0
    def test_missing_keys_allowed(self):
        """
        By default, FilterMappers treat missing keys as `None`.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id': f.Required | f.Int | f.Min(1),
                'subject': f.NotEmpty | f.MaxLength(16),
            })

        # 'subject' allows null values, so no errors are generated.
        self.assertFilterPasses(
            {
                'id': '42',
            },
            {
                'id': 42,
                'subject': None,
            },
        )

        # However, 'id' has Required in its FilterChain, so a missing
        # 'id' is still an error.
        self.assertFilterErrors(
            {
                'subject': 'Hello, world!',
            },
            {
                'id': [f.Required.CODE_EMPTY],
            },
            expected_value={
                'id': None,
                'subject': 'Hello, world!',
            },
        )
示例#6
0
    def test_extra_keys_ordered(self):
        """
        When the filter map is an OrderedDict, extra keys are
        alphabetized.
        """
        # Note that we pass an OrderedDict to the filter initializer.
        self.filter_type = lambda: f.FilterMapper(OrderedDict((
            ('subject', f.NotEmpty | f.MaxLength(16)),
            ('id', f.Required | f.Int | f.Min(1)),
        )))

        filter_ = self._filter({
            'id':       '42',
            'subject':  'Hello, world!',
            'cat':      'felix',
            'bird':     'phoenix',
            'fox':      'fennecs',
        })

        self.assertFilterPasses(
            filter_,

            OrderedDict((
                # The filtered keys are always listed first.
                ('subject', 'Hello, world!'),
                ('id', 42),

                # Extra keys are listed afterward, in alphabetical
                # order.
                ('bird', 'phoenix'),
                ('cat', 'felix'),
                ('fox', 'fennecs'),
            )),
        )
示例#7
0
    def test_extra_keys_disallowed(self):
        """
        FilterMappers can be configured to treat any extra key as an
        invalid value.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id':       f.Required | f.Int | f.Min(1),
                'subject':  f.NotEmpty | f.MaxLength(16),
            },

            # Treat all extra keys as invalid values.s
            allow_extra_keys = False,
        )

        self.assertFilterErrors(
            {
                'id':       '42',
                'subject':  'Hello, world!',
                'extra':    'ignored',
            },

            {
                'extra': [f.FilterMapper.CODE_EXTRA_KEY],
            },

            # The valid fields were still included in the return value,
            # but the invalid field was removed.
            expected_value = {
                'id':       42,
                'subject':  'Hello, world!',
            }
        )
示例#8
0
    def test_fail_match_case(self):
        """
        The incoming value matches one of the switch cases, but it is
        not valid, according to the corresponding filter.
        """
        self.assertFilterErrors(
            self._filter(
                {
                    'name': 'positive',
                    'value': -1
                },
                getter=lambda value: value['name'],
                cases={
                    'positive': f.FilterMapper({'value': f.Int | f.Min(0)}),
                },
            ),
            {'value': [f.Min.CODE_TOO_SMALL]},

            # The result is the exact same as if the value were passed
            # directly to the corresponding filter.
            expected_value={
                'name': 'positive',
                'value': None
            },
        )
示例#9
0
    def test_missing_keys_disallowed(self):
        """
        FilterMappers can be configured to treat missing keys as
        invalid values.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id':       f.Required | f.Int | f.Min(1),
                'subject':  f.NotEmpty | f.MaxLength(16),
            },

            # Treat missing keys as invalid values.
            allow_missing_keys = False,
        )

        self.assertFilterErrors(
            {},

            {
                'id':       [f.FilterMapper.CODE_MISSING_KEY],
                'subject':  [f.FilterMapper.CODE_MISSING_KEY],
            },

            expected_value = {
                'id':       None,
                'subject':  None,
            },
        )
示例#10
0
    def test_pass_ordered_mapping(self):
        """
        Configuring the FilterRepeater to return an OrderedDict.
        """
        # Note that we pass an OrderedDict to the filter initializer.
        self.filter_type = lambda: f.FilterMapper(OrderedDict((
            ('subject', f.NotEmpty | f.MaxLength(16)),
            ('id', f.Required | f.Int | f.Min(1)),
        )))

        filter_ = self._filter({
            'id':       '42',
            'subject':  'Hello, world!',
        })

        self.assertFilterPasses(
            filter_,

            OrderedDict((
                ('subject', 'Hello, world!'),
                ('id', 42),
            )),
        )

        # The result is an OrderedDict, to match the type of the filter
        # map.
        self.assertIs(type(filter_.cleaned_data), OrderedDict)
示例#11
0
 def test_pass_default(self):
     """
     The incoming value does not match any of the switch cases, but
     we defined a default filter.
     """
     self.assertFilterPasses(
         self._filter(
             {
                 'name': 'negative',
                 'value': -42
             },
             getter=lambda value: value['name'],
             cases={
                 'positive': f.FilterMapper({'value': f.Int | f.Min(0)}),
             },
             default=f.FilterMapper({'value': f.Int | f.Max(0)}),
         ), )
示例#12
0
 def __init__(
     self,
     filter_map,
     allow_missing_keys=False,
     allow_extra_keys=False,
 ):
     super(RequestFilter, self).__init__(
         f.Type(Mapping)
         | f.FilterMapper(filter_map, allow_missing_keys, allow_extra_keys))
示例#13
0
 def __init__(
     self,
     filter_map,
     allow_missing_keys=True,
     allow_extra_keys=True,
 ):
     super(ResponseFilter, self).__init__(
         f.Type(Mapping)
         | f.FilterMapper(filter_map, allow_missing_keys, allow_extra_keys))
示例#14
0
    def test_pass_none(self):
        """
        For consistency with all the other Filter classes, `None` is
        considered a valid value to pass to a FilterMapper, even
        though it is not iterable.
        """
        self.filter_type = lambda: f.FilterMapper({'id': f.Int})

        self.assertFilterPasses(None)
示例#15
0
    def test_passthru_key(self):
        """
        If you want to make a key required but do not want to run any
        Filters on it, set its FilterChain to `None`.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id':       f.Required | f.Int | f.Min(1),
                'subject':  None,
            },

            # If you configure a FilterMapper with passthru keys(s),
            # you generally also want to disallow missing keys.
            allow_missing_keys = False,
        )

        self.assertFilterPasses(
            {
                'id':       '42',
                'subject':  'Hello, world!',
            },

            {
                'id':       42,
                'subject':  'Hello, world!',
            },
        )

        self.assertFilterPasses(
            {
                'id':       '42',
                'subject':  None,
            },

            {
                'id':       42,
                'subject':  None,
            },
        )

        self.assertFilterErrors(
            {
                'id': '42',
            },

            {
                'subject': [f.FilterMapper.CODE_MISSING_KEY],
            },

            expected_value = {
                'id':       42,
                'subject':  None,
            },
        )
示例#16
0
    def test_fail_non_mapping(self):
        """The incoming value is not a mapping."""
        self.filter_type = lambda: f.FilterMapper({
            'id':       f.Required | f.Int | f.Min(1),
            'subject':  f.NotEmpty | f.MaxLength(16),
        })

        self.assertFilterErrors(
            # Nope; it's gotta be an explicit mapping.
            (('id', '42'), ('subject', 'Hello, world!')),
            [f.Type.CODE_WRONG_TYPE],
        )
示例#17
0
    def test_extra_keys_specified(self):
        """
        FilterMappers can be configured only to allow certain extra
        keys.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id':       f.Required | f.Int | f.Min(1),
                'subject':  f.NotEmpty | f.MaxLength(16),
            },

            allow_extra_keys = {'message', 'extra'},
        )

        # As long as the extra keys are in the FilterMapper's
        # ``allow_extra_keys`` setting, everything is fine.
        self.assertFilterPasses(
            {
                'id':       '42',
                'subject':  'Hello, world!',
                'extra':    'ignored',
            },

            {
                'id':       42,
                'subject':  'Hello, world!',
                'extra':    'ignored',
            },
        )

        # But, add a key that isn't in ``allow_extra_keys``, and you've
        # got a problem.
        self.assertFilterErrors(
            {
                'id':           '42',
                'subject':      'Hello, world!',
                'attachment':   {
                    'type':         'image/jpeg',
                    'data':         '...',
                },
            },

            {
                'attachment': [f.FilterMapper.CODE_EXTRA_KEY],
            },

            expected_value = {
                'id':       42,
                'subject':  'Hello, world!',
            }
        )
示例#18
0
 def test_pass_match_case(self):
     """
     The incoming value matches one of the switch cases.
     """
     self.assertFilterPasses(
         self._filter(
             {
                 'name': 'positive',
                 'value': 42
             },
             getter=lambda value: value['name'],
             cases={
                 'positive': f.FilterMapper({'value': f.Int | f.Min(0)}),
             },
         ), )
示例#19
0
 def test_fail_no_default(self):
     """
     The incoming value does not match any of the switch cases, and
     we did not define a default filter.
     """
     self.assertFilterErrors(
         self._filter(
             {
                 'name': 'negative',
                 'value': -42
             },
             getter=lambda value: value['name'],
             cases={
                 'positive': f.FilterMapper({'value': f.Int | f.Min(0)}),
             },
         ),
         [f.Choice.CODE_INVALID],
     )
示例#20
0
    def test_extra_keys_allowed(self):
        """
        By default, FilterMappers passthru extra keys.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id': f.Required | f.Int | f.Min(1),
                'subject': f.NotEmpty | f.MaxLength(16),
            })

        self.assertFilterPasses(
            {
                'id': '42',
                'subject': 'Hello, world!',
                'extra': 'ignored',
            }, {
                'id': 42,
                'subject': 'Hello, world!',
                'extra': 'ignored',
            })
示例#21
0
    def test_filter_mapper_chained_with_filter(self):
        """
        Chaining a Filter with a FilterMapper causes the chained Filter
        to operate on the entire mapping.
        """
        fm = f.FilterMapper({
            'id':       f.Required | f.Int | f.Min(1),
            'subject':  f.NotEmpty | f.MaxLength(16),
        })

        self.filter_type = lambda: fm | f.MaxLength(3)

        self.assertFilterPasses(
            {
                'id':       '42',
                'subject':  'Hello, world!',
                'extra':    'ignored',
            },

            {
                'id':       42,
                'subject':  'Hello, world!',
                'extra':    'ignored',
            },
        )

        self.assertFilterErrors(
            {
                'id':           '42',
                'subject':      'Hello, world!',
                'extra':        'ignored',
                'attachment':   None,
            },

            # The incoming value has 4 items, which fails the MaxLength
            # filter.
            [f.MaxLength.CODE_TOO_LONG],
        )
示例#22
0
    def test_mapperception(self):
        """
        Want to filter dicts that contain other dicts?
        We need to go deeper.
        """
        self.filter_type = lambda: f.FilterMapper(
            {
                'id':           f.Required | f.Int | f.Min(1),
                'subject':      f.NotEmpty | f.MaxLength(16),
                'attachment':   f.FilterMapper(
                    {
                        'type':
                                f.Required
                            |   f.Choice(choices={'image/jpeg', 'image/png'}),

                        'data': f.Required | f.Base64Decode,
                    },

                    allow_extra_keys    = False,
                    allow_missing_keys  = False,
                )
            },

            allow_extra_keys    = False,
            allow_missing_keys  = False,
        )

        # Valid mapping is valid.
        self.assertFilterPasses(
            {
                'id':      '42',
                'subject': 'Hello, world!',

                'attachment': {
                    'type': 'image/jpeg',

                    'data':
                        b'R0lGODlhDwAPAKECAAAAzMzM/////wAAACwAAAAAD'
                        b'wAPAAACIISPeQHsrZ5ModrLlN48CXF8m2iQ3YmmKq'
                        b'VlRtW4MLwWACH+EVRIRSBDQUtFIElTIEEgTElFOw==',
                },
            },

            {
                'id':       42,
                'subject':  'Hello, world!',

                'attachment': {
                    'type': 'image/jpeg',

                    'data':
                        b'GIF89a\x0f\x00\x0f\x00\xa1\x02\x00\x00\x00'
                        b'\xcc\xcc\xcc\xff\xff\xff\xff\x00\x00\x00,\x00'
                        b'\x00\x00\x00\x0f\x00\x0f\x00\x00\x02 \x84\x8f'
                        b'y\x01\xec\xad\x9eL\xa1\xda\xcb\x94\xde<\tq|'
                        b'\x9bh\x90\xdd\x89\xa6*\xa5eF\xd5\xb80\xbc\x16'
                        b'\x00!\xfe\x11THE CAKE IS A LIE;'
                },
            },
        )

        # Invalid mapping... not so much.
        self.assertFilterErrors(
            {
                'id': 'NaN',

                'attachment': {
                    'type': 'foo',
                    'data': False,
                },
            },

            {
                # The error keys are the dotted paths to the invalid
                # values.
                # This way, we don't have to deal with nested dicts
                # when processing error codes.
                'id':               [f.Decimal.CODE_NON_FINITE],
                'subject':          [f.FilterMapper.CODE_MISSING_KEY],
                'attachment.type':  [f.Choice.CODE_INVALID],
                'attachment.data':  [f.Type.CODE_WRONG_TYPE],
            },

            # The resulting value has the expected structure, but it's
            # a ghost town.
            expected_value = {
                'id':       None,
                'subject':  None,

                'attachment': {
                    'type': None,
                    'data': None,
                },
            },
        )
示例#23
0
    def filter_kwargs(self, filters):
        # type: (Dict[Text, Dict[Text, f.FilterCompatible]]) -> Dict[Text, dict]
        """
        Extracts and filters values from the trigger kwargs.

        :param filters:
            Keys are the names of triggers to extract params from.
            Values are dicts used to configure FilterMapper instances.

            Note: The FilterMapper instances are configured with:
            - ``allow_missing_keys = True``
            - ``allow_extra_keys = True``

        Example::

           task_params =
             context.filter_kwargs({
               't_createApplicant': {
                 'eflId': f.Required | f.ext.Model(Questionnaire),
               },

               'scoring': {
                  'model'; f.Required | f.Unicode,
               },
             })

        :raise:
          - ``ValueError`` if the trigger kwargs fail validation.
        """
        # Configure the inner filters, used to process each value
        # inside ``trigger_kwargs``.
        map_ = {
            item_key: f.Optional(default={}) | f.FilterMapper(filter_map)
            for item_key, filter_map in iteritems(filters)
        }

        filter_ =\
            f.FilterRunner(
                # Configure the outer filter, used to apply the inner
                # filters to the ``trigger_kwargs`` dict.
                starting_filter =
                    f.FilterMapper(
                        filter_map          = map_,
                        allow_missing_keys  = True,
                        allow_extra_keys    = True,
                    ),

                incoming_data = self.trigger_kwargs or {},
            )

        if not filter_.is_valid():
            raise with_context(
                exc=ValueError(
                    'Invalid trigger kwargs: {errors}'.format(
                        errors=filter_.errors, ), ),
                context={
                    'filter_errors': filter_.get_errors(with_context=True),
                },
            )

        return filter_.cleaned_data