コード例 #1
0
ファイル: resources.py プロジェクト: thedrow/odin
    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)
コード例 #2
0
ファイル: decorators.py プロジェクト: python-odin/odinweb
    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)
コード例 #3
0
ファイル: resources.py プロジェクト: sathish86/odin
    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)
コード例 #4
0
ファイル: future.py プロジェクト: ktp-forked-repos/odin
        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)
コード例 #5
0
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
コード例 #6
0
ファイル: resources.py プロジェクト: thedrow/odin
    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)
コード例 #7
0
ファイル: resources.py プロジェクト: thedrow/odin
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
コード例 #8
0
ファイル: test_value.py プロジェクト: python-odin/odin3
 def __call__(self, value):
     if self.fail:
         raise ValidationError(code=self.code, message=self.message, params=self.params)
コード例 #9
0
ファイル: test_resources.py プロジェクト: thedrow/odin
 def clean_country(self, value):
     if value not in ["Australia", "New Zealand"]:
         raise ValidationError("What are ya?")
     return "%s!" % value
コード例 #10
0
ファイル: test_resources.py プロジェクト: thedrow/odin
 def clean(self):
     if self.name == "Bruce" and self.country.startswith("Australia"):
         raise ValidationError("No no no no")
コード例 #11
0
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)),
            }
        }