Source code for flask_restplus.mask

# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import

import logging
import re
import six

from collections import OrderedDict
from inspect import isclass

from .errors import RestError

log = logging.getLogger(__name__)

LEXER = re.compile(r'\{|\}|\,|[\w_:\-\*]+')


[docs]class MaskError(RestError): '''Raised when an error occurs on mask''' pass
[docs]class ParseError(MaskError): '''Raised when the mask parsing failed''' pass
[docs]class Mask(OrderedDict): ''' Hold a parsed mask. :param str|dict|Mask mask: A mask, parsed or not :param bool skip: If ``True``, missing fields won't appear in result ''' def __init__(self, mask=None, skip=False, **kwargs): self.skip = skip if isinstance(mask, six.string_types): super(Mask, self).__init__() self.parse(mask) elif isinstance(mask, (dict, OrderedDict)): super(Mask, self).__init__(mask, **kwargs) else: self.skip = skip super(Mask, self).__init__(**kwargs)
[docs] def parse(self, mask): ''' Parse a fields mask. Expect something in the form:: {field,nested{nested_field,another},last} External brackets are optionals so it can also be written:: field,nested{nested_field,another},last All extras characters will be ignored. :param str mask: the mask string to parse :raises ParseError: when a mask is unparseable/invalid ''' if not mask: return mask = self.clean(mask) fields = self previous = None stack = [] for token in LEXER.findall(mask): if token == '{': if previous not in fields: raise ParseError('Unexpected opening bracket') fields[previous] = Mask(skip=self.skip) stack.append(fields) fields = fields[previous] elif token == '}': if not stack: raise ParseError('Unexpected closing bracket') fields = stack.pop() elif token == ',': if previous in (',', '{', None): raise ParseError('Unexpected comma') else: fields[token] = True previous = token if stack: raise ParseError('Missing closing bracket')
[docs] def clean(self, mask): '''Remove unecessary characters''' mask = mask.replace('\n', '').strip() # External brackets are optional if mask[0] == '{': if mask[-1] != '}': raise ParseError('Missing closing bracket') mask = mask[1:-1] return mask
[docs] def apply(self, data): ''' Apply a fields mask to the data. :param data: The data or model to apply mask on :raises MaskError: when unable to apply the mask ''' from . import fields # Should handle lists if isinstance(data, (list, tuple, set)): return [self.apply(d) for d in data] elif isinstance(data, (fields.Nested, fields.List, fields.Polymorph)): return data.clone(self) elif type(data) == fields.Raw: return fields.Raw(default=data.default, attribute=data.attribute, mask=self) elif data == fields.Raw: return fields.Raw(mask=self) elif isinstance(data, fields.Raw) or isclass(data) and issubclass(data, fields.Raw): # Not possible to apply a mask on these remaining fields types raise MaskError('Mask is inconsistent with model') # Should handle objects elif (not isinstance(data, (dict, OrderedDict)) and hasattr(data, '__dict__')): data = data.__dict__ return self.filter_data(data)
[docs] def filter_data(self, data): ''' Handle the data filtering given a parsed mask :param dict data: the raw data to filter :param list mask: a parsed mask tofilter against :param bool skip: whether or not to skip missing fields ''' out = {} for field, content in six.iteritems(self): if field == '*': continue elif isinstance(content, Mask): nested = data.get(field, None) if self.skip and nested is None: continue elif nested is None: out[field] = None else: out[field] = content.apply(nested) elif self.skip and field not in data: continue else: out[field] = data.get(field, None) if '*' in self.keys(): for key, value in six.iteritems(data): if key not in out: out[key] = value return out
def __str__(self): return '{{{0}}}'.format(','.join([ ''.join((k, str(v))) if isinstance(v, Mask) else k for k, v in six.iteritems(self) ]))
[docs]def apply(data, mask, skip=False): ''' Apply a fields mask to the data. :param data: The data or model to apply mask on :param str|Mask mask: the mask (parsed or not) to apply on data :param bool skip: If rue, missing field won't appear in result :raises MaskError: when unable to apply the mask ''' return Mask(mask, skip).apply(data)