class GeocodeParameters(Parameters): """ Helper Parameters class to reuse geocoding. """ search = base_fields.String( description="the type of query we want to search", ) filters = base_fields.Raw() coordinates = base_fields.List( cls_or_instance=base_fields.List( cls_or_instance=base_fields.Float() ), allow_none=True, ) latitude = base_fields.Float( allow_none=True, description="the latitude to search in", validate=validate.Range(min=-90.0, max=90.0, error="Invalid latitude parameters. Must be between -90 and 90.") ) longitude = base_fields.Float( allow_none=True, description="the latitude to search in", validate=validate.Range(min=-180.0, max=180.0, error="Invalid longitude parameters. Must be between -180 and 180.") ) radius = base_fields.Float( description="the radius to search in", missing=5, validate=validate.Range(min=0) )
class BasicPagerSchema(ModelSchema): has_prev = base_fields.Boolean() has_next = base_fields.Boolean() pages = base_fields.Integer() page = base_fields.Integer() lst_size = base_fields.Integer() total = base_fields.Integer() items = base_fields.Raw() class Meta: pass
class PatchJSONParameters(Parameters): """ Base parameters class for handling PATCH arguments according to RFC 6902. """ # All operations described in RFC 6902 OP_ADD = 'add' OP_REMOVE = 'remove' OP_REPLACE = 'replace' OP_MOVE = 'move' OP_COPY = 'copy' OP_TEST = 'test' # However, we use only those which make sense in RESTful API OPERATION_CHOICES = ( OP_TEST, OP_ADD, OP_REMOVE, OP_REPLACE, ) op = base_fields.String(required=True) # pylint: disable=invalid-name PATH_CHOICES = None path = base_fields.String(required=True) value = base_fields.Raw(required=False) def __init__(self, *args, **kwargs): super(PatchJSONParameters, self).__init__(*args, many=True, **kwargs) if not self.PATH_CHOICES: raise ValueError("%s.PATH_CHOICES has to be set" % self.__class__.__name__) # Make a copy of `validators` as otherwise we will modify the behaviour # of all `marshmallow.Schema`-based classes self.fields['op'].validators = \ self.fields['op'].validators + [validate.OneOf(self.OPERATION_CHOICES)] self.fields['path'].validators = \ self.fields['path'].validators + [validate.OneOf(self.PATH_CHOICES)]
class PatchJSONParameters(Parameters): """ Base parameters class for handling PATCH arguments according to RFC 6902. """ # All operations described in RFC 6902 OP_ADD = 'add' OP_REMOVE = 'remove' OP_REPLACE = 'replace' OP_MOVE = 'move' OP_COPY = 'copy' OP_TEST = 'test' # However, we use only those which make sense in RESTful API OPERATION_CHOICES = ( OP_TEST, OP_ADD, OP_REMOVE, OP_REPLACE, ) op = base_fields.String(required=True) # pylint: disable=invalid-name PATH_CHOICES = None path = base_fields.String(required=True) NO_VALUE_OPERATIONS = (OP_REMOVE,) value = base_fields.Raw(required=False) def __init__(self, *args, **kwargs): if 'many' in kwargs: assert kwargs['many'], "PATCH Parameters must be marked as 'many'" kwargs['many'] = True super(PatchJSONParameters, self).__init__(*args, **kwargs) if not self.PATH_CHOICES: raise ValueError('%s.PATH_CHOICES has to be set' % self.__class__.__name__) # Make a copy of `validators` as otherwise we will modify the behaviour # of all `marshmallow.Schema`-based classes self.fields['op'].validators = self.fields['op'].validators + [ validate.OneOf(self.OPERATION_CHOICES) ] self.fields['path'].validators = self.fields['path'].validators + [ validate.OneOf(self.PATH_CHOICES) ] @validates_schema def validate_patch_structure(self, data): """ Common validation of PATCH structure Provide check that 'value' present in all operations expect it. Provide check if 'path' is present. 'path' can be absent if provided without '/' at the start. Supposed that if 'path' is present than it is prepended with '/'. Removing '/' in the beginning to simplify usage in resource. """ if data['op'] not in self.NO_VALUE_OPERATIONS and 'value' not in data: raise ValidationError('value is required') if 'path' not in data: raise ValidationError('Path is required and must always begin with /') else: data['field_name'] = data['path'][1:] @classmethod def perform_patch(cls, operations, obj, state=None): """ Performs all necessary operations by calling class methods with corresponding names. """ if state is None: state = {} for operation in operations: if not cls._process_patch_operation(operation, obj=obj, state=state): log.info( '%s patching has been stopped because of unknown operation %s', obj.__class__.__name__, operation, ) raise ValidationError( 'Failed to update %s details. Operation %s could not succeed.' % (obj.__class__.__name__, operation) ) return True @classmethod def _process_patch_operation(cls, operation, obj, state): """ Args: operation (dict): one patch operation in RFC 6902 format. obj (object): an instance which is needed to be patched. state (dict): inter-operations state storage Returns: processing_status (bool): True if operation was handled, otherwise False. """ field_operaion = operation['op'] if field_operaion == cls.OP_REPLACE: return cls.replace( obj, operation['field_name'], operation['value'], state=state ) elif field_operaion == cls.OP_TEST: return cls.test(obj, operation['field_name'], operation['value'], state=state) elif field_operaion == cls.OP_ADD: return cls.add(obj, operation['field_name'], operation['value'], state=state) elif field_operaion == cls.OP_MOVE: return cls.move(obj, operation['field_name'], operation['value'], state=state) elif field_operaion == cls.OP_COPY: return cls.copy(obj, operation['field_name'], operation['value'], state=state) elif field_operaion == cls.OP_REMOVE: # This deviates from RFC 6902 to permit field and value based removal. # This is used for multiple relationship tables within houston return cls.remove( obj, operation['field_name'], operation.get('value', None), state=state ) return False @classmethod def replace(cls, obj, field, value, state): """ This is method for replace operation. It is separated to provide a possibility to easily override it in your Parameters. Args: obj (object): an instance to change. field (str): field name value (str): new value state (dict): inter-operations state storage Returns: processing_status (bool): True """ # Check for existence if not hasattr(obj, field): raise ValidationError( "Field '%s' does not exist, so it cannot be patched" % field ) # Check for Enum objects try: obj_cls = obj.__class__ obj_column = getattr(obj_cls, field) obj_column_type = obj_column.expression.type if isinstance(obj_column_type, sa.sql.sqltypes.Enum): enum_values = obj_column_type.enums if value not in enum_values: args = (field, value, enum_values) raise ValidationError( "Field '%s' is an Enum and does not recognize the value '%s'. Please select one of %r" % args ) except (AttributeError): pass # Set the value setattr(obj, field, value) return True @classmethod def test(cls, obj, field, value, state): """ This is method for test operation. It is separated to provide a possibility to easily override it in your Parameters. Args: obj (object): an instance to change. field (str): field name value (str): new value state (dict): inter-operations state storage Returns: processing_status (bool): True """ return getattr(obj, field) == value @classmethod def add(cls, obj, field, value, state): raise NotImplementedError() @classmethod def remove(cls, obj, field, value, state): """ This is method for removal operation. It is separated to provide a possibility to easily override it in your Parameters. Args: obj (object): an instance to change. field (str): field name value (str): [optional] item to remove for lists, Extension on RFC 6509 state (dict): inter-operations state storage Returns: processing_status (bool): True """ raise NotImplementedError() @classmethod def move(cls, obj, field, value, state): raise NotImplementedError() @classmethod def copy(cls, obj, field, value, state): raise NotImplementedError()
class PatchJSONParameters(Parameters): """ Base parameters class for handling PATCH arguments according to RFC 6902. """ # All operations described in RFC 6902 OP_ADD = "add" OP_REMOVE = "remove" OP_REPLACE = "replace" OP_MOVE = "move" OP_COPY = "copy" OP_TEST = "test" # However, we use only those which make sense in RESTful API OPERATION_CHOICES = ( OP_TEST, OP_ADD, OP_REMOVE, OP_REPLACE, ) op = base_fields.String(required=True) # pylint: disable=invalid-name PATH_CHOICES = None path = base_fields.String(required=True) NO_VALUE_OPERATIONS = (OP_REMOVE, ) value = base_fields.Raw(required=False) def __init__(self, *args, **kwargs): if "many" in kwargs: assert kwargs["many"], "PATCH Parameters must be marked as 'many'" kwargs["many"] = True super(PatchJSONParameters, self).__init__(*args, **kwargs) if not self.PATH_CHOICES: raise ValueError("%s.PATH_CHOICES has to be set" % self.__class__.__name__) # Make a copy of `validators` as otherwise we will modify the behaviour # of all `marshmallow.Schema`-based classes self.fields["op"].validators = self.fields["op"].validators + [ validate.OneOf(self.OPERATION_CHOICES) ] self.fields["path"].validators = self.fields["path"].validators + [ validate.OneOf(self.PATH_CHOICES) ] @validates_schema def validate_patch_structure(self, data): """ Common validation of PATCH structure Provide check that 'value' present in all operations expect it. Provide check if 'path' is present. 'path' can be absent if provided without '/' at the start. Supposed that if 'path' is present than it is prepended with '/'. Removing '/' in the beginning to simplify usage in resource. """ if data["op"] not in self.NO_VALUE_OPERATIONS and "value" not in data: raise ValidationError("value is required") if "path" not in data: raise ValidationError( "Path is required and must always begin with /") else: data["field_name"] = data["path"][1:] @classmethod def perform_patch(cls, operations, obj, state=None): """ Performs all necessary operations by calling class methods with corresponding names. """ if state is None: state = {} for operation in operations: if not cls._process_patch_operation( operation, obj=obj, state=state): log.info( "%s patching has been stopped because of unknown operation %s", obj.__class__.__name__, operation, ) raise ValidationError( "Failed to update %s details. Operation %s could not succeed." % (obj.__class__.__name__, operation)) return True @classmethod def _process_patch_operation(cls, operation, obj, state): """ Args: operation (dict): one patch operation in RFC 6902 format. obj (object): an instance which is needed to be patched. state (dict): inter-operations state storage Returns: processing_status (bool): True if operation was handled, otherwise False. """ field_operaion = operation["op"] if field_operaion == cls.OP_REPLACE: return cls.replace(obj, operation["field_name"], operation["value"], state=state) elif field_operaion == cls.OP_TEST: return cls.test(obj, operation["field_name"], operation["value"], state=state) elif field_operaion == cls.OP_ADD: return cls.add(obj, operation["field_name"], operation["value"], state=state) elif field_operaion == cls.OP_MOVE: return cls.move(obj, operation["field_name"], operation["value"], state=state) elif field_operaion == cls.OP_COPY: return cls.copy(obj, operation["field_name"], operation["value"], state=state) elif field_operaion == cls.OP_REMOVE: return cls.remove(obj, operation["field_name"], state=state) return False @classmethod def replace(cls, obj, field, value, state): """ This is method for replace operation. It is separated to provide a possibility to easily override it in your Parameters. Args: obj (object): an instance to change. field (str): field name value (str): new value state (dict): inter-operations state storage Returns: processing_status (bool): True """ if not hasattr(obj, field): raise ValidationError( "Field '%s' does not exist, so it cannot be patched" % field) setattr(obj, field, value) return True @classmethod def test(cls, obj, field, value, state): """ This is method for test operation. It is separated to provide a possibility to easily override it in your Parameters. Args: obj (object): an instance to change. field (str): field name value (str): new value state (dict): inter-operations state storage Returns: processing_status (bool): True """ return getattr(obj, field) == value @classmethod def add(cls, obj, field, value, state): raise NotImplementedError() @classmethod def remove(cls, obj, field, state): raise NotImplementedError() @classmethod def move(cls, obj, field, value, state): raise NotImplementedError() @classmethod def copy(cls, obj, field, value, state): raise NotImplementedError()
class BasicSchema(ModelSchema): status = base_fields.Integer(default=Status.SUCCESS.status) message = base_fields.String(default=Status.SUCCESS.message) data = base_fields.Raw() class Meta: pass