def clean_fields(self, exclude=None, ignore_not_provided=False): errors = {} meta = getmeta(self) for f in meta.fields: if exclude and f.name in exclude: continue raw_value = f.value_from_object(self) if (f.null and raw_value is None) or (ignore_not_provided and raw_value is NotProvided): continue try: raw_value = f.clean(raw_value) except ValidationError as e: errors[f.name] = e.messages # Check for resource level clean methods. clean_method = getattr(self, "clean_%s" % f.attname, None) if callable(clean_method): try: raw_value = clean_method(raw_value) except ValidationError as e: errors.setdefault(f.name, []).extend(e.messages) if f not in meta.readonly_fields: setattr(self, f.attname, raw_value) if errors: raise ValidationError(errors)
def execute(self, request, *args, **path_args): # type: (BaseHttpRequest, *Any, **Any) -> Any errors = {} headers = {} # Parse query strings for field in self._query_fields: value = request.GET.get(field.name, field.default) try: value = field.clean(value) except ValidationError as ve: errors[field.name] = ve.messages else: path_args[field.name] = value headers['X-Page-{}'.format(field.name.title())] = str(value) if errors: raise ValidationError(errors) # Run base execute result = super(ListOperation, self).execute(request, *args, **path_args) if result is not None: if isinstance(result, tuple) and len(result) == 2: result, total_count = result if total_count is not None: headers['X-Total-Count'] = str(total_count) return create_response(request, result, headers=headers)
def clean_fields(self, exclude=None): errors = {} for f in self._meta.fields: if exclude and f.name in exclude: continue raw_value = f.value_from_object(self) if f.null and raw_value is None: continue try: raw_value = f.clean(raw_value) except ValidationError as e: errors[f.name] = e.messages # Check for resource level clean methods. clean_method = getattr(self, "clean_%s" % f.attname, None) if callable(clean_method): try: raw_value = clean_method(raw_value) except ValidationError as e: errors.setdefault(f.name, []).extend(e.messages) setattr(self, f.attname, raw_value) if errors: raise ValidationError(errors)
def to_python(self, value): # type: (Any) -> Optional[ET] if value is None: return # Attempt to convert try: return self.enum(value) except ValueError: raise ValidationError(self.error_messages['invalid_choice'] % value)
def create_resource_from_iter(i, resource, full_clean=True, default_to_not_provided=False): """ Create a resource from an iterable sequence :param i: Iterable of values (it is assumed the values are in field order) :param resource: A resource type, resource name or list of resources and names to use as the base for creating a resource. :param full_clean: Perform a full clean as part of the creation, this is useful for parsing data with known columns (eg CSV data). :param default_to_not_provided: If an value is not supplied keep the value as NotProvided. This is used to support merging an updated value. :return: New instance of resource type specified in the *resource* param. """ i = list(i) resource_type = resource fields = getmeta(resource_type).fields # Optimisation to allow the assumption that len(fields) == len(i) len_fields = len(fields) len_i = len(i) extra = None if len_i < len_fields: i += [NotProvided] * (len_fields - len_i) elif len_i > len_fields: extra = i[len_fields:] i = i[:len_fields] attrs = [] errors = {} for f, value in zip(fields, i): if value is NotProvided: if not default_to_not_provided: value = f.get_default( ) if f.use_default_if_not_provided else None else: try: value = f.to_python(value) except ValidationError as ve: errors[f.name] = ve.error_messages attrs.append(value) if errors: raise ValidationError(errors) new_resource = resource_type(*attrs) if extra: new_resource.extra_attrs(extra) if full_clean: new_resource.full_clean() return new_resource
def full_clean(self, exclude=None, ignore_not_provided=False): """ Calls clean_fields, clean on the resource and raises ``ValidationError`` for any errors that occurred. """ errors = {} try: self.clean_fields(exclude, ignore_not_provided) except ValidationError as e: errors = e.update_error_dict(errors) try: self.clean() except ValidationError as e: errors = e.update_error_dict(errors) if errors: raise ValidationError(errors)
def create_resource_from_dict(d, resource=None, full_clean=True, copy_dict=True, default_to_not_provided=False): # type: (Dict[str, Any], R, bool, bool, bool) -> Instance[R] """ Create a resource from a dict. :param d: dictionary of data. :param resource: A resource type, resource name or list of resources and names to use as the base for creating a resource. If a list is supplied the first item will be used if a resource type is not supplied; this could also be a parent(s) of any resource defined by the dict. :param full_clean: Perform a full clean as part of the creation. :param copy_dict: Use a copy of the input dictionary rather than destructively processing the input dict. :param default_to_not_provided: If an value is not supplied keep the value as NOT_PROVIDED. This is used to support merging an updated value. """ assert isinstance(d, dict) if copy_dict: d = d.copy() if resource: resource_type = None # Convert to single resource then resolve document type if isinstance(resource, (tuple, list)): resources = (resolve_resource_type(r) for r in resource) else: resources = [resolve_resource_type(resource)] for resource_name, type_field in resources: # See if the input includes a type field and check it's registered document_resource_name = d.get(type_field, None) if document_resource_name: resource_type = registration.get_resource( document_resource_name) else: resource_type = registration.get_resource(resource_name) if not resource_type: raise exceptions.ResourceException( "Resource `%s` is not registered." % document_resource_name) if document_resource_name: # Check resource types match or are inherited types if (resource_name == document_resource_name or resource_name in getmeta(resource_type).parent_resource_names): break # We are done else: break if not resource_type: raise exceptions.ResourceException( "Incoming resource does not match [%s]" % ', '.join(r for r, t in resources)) else: # No resource specified, relay on type field document_resource_name = d.pop(DEFAULT_TYPE_FIELD, None) if not document_resource_name: raise exceptions.ResourceException("Resource not defined.") # Get an instance of a resource type resource_type = registration.get_resource(document_resource_name) if not resource_type: raise exceptions.ResourceException( "Resource `%s` is not registered." % document_resource_name) attrs = [] errors = {} meta = getmeta(resource_type) for f in meta.init_fields: value = d.pop(f.name, NotProvided) if value is NotProvided: if not default_to_not_provided: value = f.get_default( ) if f.use_default_if_not_provided else None else: try: value = f.to_python(value) except ValidationError as ve: errors[f.name] = ve.error_messages attrs.append(value) if errors: raise ValidationError(errors) new_resource = resource_type(*attrs) if d: new_resource.extra_attrs(d) if full_clean: new_resource.full_clean() return new_resource
def __call__(self, value): if self.fail: raise ValidationError(code=self.code, message=self.message, params=self.params)
def clean_country(self, value): if value not in ["Australia", "New Zealand"]: raise ValidationError("What are ya?") return "%s!" % value
def clean(self): if self.name == "Bruce" and self.country.startswith("Australia"): raise ValidationError("No no no no")
class TestApiInterfaceBase(object): @pytest.mark.parametrize('options,name,debug_enabled,path_prefix', ( ({}, 'api', False, UrlPath.parse('/api')), ({ 'name': '!api' }, '!api', False, UrlPath.parse('/!api')), ({ 'path_prefix': '/my-app/' }, 'api', False, UrlPath.parse('/my-app')), ({ 'debug_enabled': True }, 'api', True, UrlPath.parse('/api')), )) def test_options(self, options, name, debug_enabled, path_prefix): target = containers.ApiInterfaceBase(**options) assert target.name == name assert target.debug_enabled == debug_enabled assert target.path_prefix == path_prefix def test_init_non_absolute(self): with pytest.raises(ValueError): containers.ApiInterfaceBase(path_prefix='ab/c') def test_dispatch(self): pass @pytest.mark.parametrize('r, status, message', ( (MockRequest(headers={ 'content-type': 'application/xml', 'accepts': 'application/json' }), 422, 'Unprocessable Entity'), (MockRequest(headers={ 'content-type': 'application/json', 'accepts': 'application/xml' }), 406, 'URI not available in preferred format'), (MockRequest(method=Method.POST), 405, 'Specified method is invalid for this resource'), )) def test_dispatch__invalid_headers(self, r, status, message): target = containers.ApiInterfaceBase() operation = Operation(mock_callback) actual = target.dispatch(operation, r) assert actual.status == status assert actual.body == message @pytest.mark.parametrize('error,status', ( (api.ImmediateHttpResponse(None, HTTPStatus.NOT_MODIFIED, {}), HTTPStatus.NOT_MODIFIED), (ValidationError("Error"), 400), (ValidationError({}), 400), (NotImplementedError, 501), (ValueError, 500), (api.ImmediateHttpResponse(ValueError, HTTPStatus.NOT_MODIFIED, {}), 500), )) def test_dispatch__exceptions(self, error, status): def callback(request): raise error target = containers.ApiInterfaceBase() operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.status == status def test_dispatch__with_middleware(self): calls = [] class Middleware(object): def pre_request(self, request, path_args): calls.append('pre_request') def pre_dispatch(self, request, path_args): calls.append('pre_dispatch') path_args['foo'] = 'bar' def post_dispatch(self, request, response): calls.append('post_dispatch') return 'eek' + response def post_request(self, request, response): calls.append('post_request') response['test'] = 'header' return response def callback(request, **args): assert args['foo'] == 'bar' return 'boo' target = containers.ApiInterfaceBase(middleware=[Middleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.body == '"eekboo"' assert actual.status == 200 assert 'test' in actual.headers assert calls == [ 'pre_request', 'pre_dispatch', 'post_dispatch', 'post_request' ] def test_dispatch__with_middleware_pre_request_response(self): """ Test scenario where pre-request hook returns a HTTP Response object """ class Middleware(object): def pre_request(self, request, path_args): return HttpResponse('eek!', status=HTTPStatus.FORBIDDEN) def callback(request, **args): assert False, "Response should have already occurred!" target = containers.ApiInterfaceBase(middleware=[Middleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.body == 'eek!' assert actual.status == 403 def test_dispatch__error_with_debug_enabled(self): def callback(request): raise ValueError() target = containers.ApiInterfaceBase(debug_enabled=True) operation = Operation(callback) with pytest.raises(ValueError): target.dispatch(operation, MockRequest()) def test_dispatch__error_handled_by_middleware(self): class ErrorMiddleware(object): def handle_500(self, request, exception): assert isinstance(exception, ValueError) return Error.from_status(HTTPStatus.SEE_OTHER, 0, "Quick over there...") def callback(request): raise ValueError() target = containers.ApiInterfaceBase(middleware=[ErrorMiddleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.status == 303 def test_dispatch__error_handled_by_middleware_raises_exception(self): class ErrorMiddleware(object): def handle_500(self, request, exception): assert isinstance(exception, ValueError) raise ValueError def callback(request): raise ValueError() target = containers.ApiInterfaceBase(middleware=[ErrorMiddleware()]) operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.status == 500 def test_dispatch__encode_error_with_debug_enabled(self): def callback(request): raise api.ImmediateHttpResponse(ValueError, HTTPStatus.NOT_MODIFIED, {}) target = containers.ApiInterfaceBase(debug_enabled=True) operation = Operation(callback) with pytest.raises(TypeError): target.dispatch(operation, MockRequest()) def test_dispatch__http_response(self): def callback(request): return HttpResponse("eek") target = containers.ApiInterfaceBase() operation = Operation(callback) actual = target.dispatch(operation, MockRequest()) assert actual.body == 'eek' assert actual.status == 200 def test_op_paths(self): target = containers.ApiInterfaceBase(MockResourceApi()) actual = list(target.op_paths()) assert actual == [ (UrlPath.parse('/api/a/b'), Operation(mock_callback, 'a/b', Method.GET)), (UrlPath.parse('/api/a/b'), Operation(mock_callback, 'a/b', Method.POST)), (UrlPath.parse('/api/d/e'), Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH))), ] def test_op_paths__collate_methods(self): target = containers.ApiInterfaceBase(MockResourceApi()) actual = target.op_paths(collate_methods=True) assert actual == { UrlPath.parse('/api/a/b'): { Method.GET: Operation(mock_callback, 'a/b', Method.GET), Method.POST: Operation(mock_callback, 'a/b', Method.POST), }, UrlPath.parse('/api/d/e'): { Method.POST: Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH)), Method.PATCH: Operation(mock_callback, 'd/e', (Method.POST, Method.PATCH)), } }