示例#1
0
    async def head(self):
        """
        Handles HEAD request.

        1. Validates GET parameters using GET input schema and validator.
        2. Fetches total amount of items and returns it in X-Total header.
        3. Finishes response.

        :raises: In case of bad query parameters - HTTPError 400.
        """
        await self.validate(
            {
                k: self.get_argument(k)
                for k in self.request.query_arguments.keys()
            }, self.get_schema_input)
        try:
            qs = self.get_queryset(paginate=False)
        except AttributeError as e:
            # Wrong field name in filter or order_by
            # Request.body is not available in HEAD request
            # No detail info will be provided
            raise HTTPError(400)
        try:
            total_num = await self.application.objects.count(qs)
        except (peewee.DataError, peewee.ProgrammingError, ValueError) as e:
            # Bad parameters
            # Request.body is not available in HEAD request
            # No detail info will be provided
            raise HTTPError(400)
        self.set_header('X-Total', total_num)
        self.finish()
示例#2
0
    async def post(self):
        """
        Handles POST request.
        Validates data and creates new item.
        Returns serialized object written to response.

        HTTPError 405 is raised in case of not creatable model (there must be
        _create method implemented in model class).

        HTTPError 400 is raised in case of violated constraints, invalid
        parameters and other data and integrity errors.

        :raises: HTTPError 405, 400
        """
        data = await self.validate(self.request.body, self.post_schema_input)
        try:
            item = await self.model_cls._create(self.application, data)
        except AttributeError as e:
            # We can only create item if _create() model method implemented
            err = xhtml_escape(str(e))
            raise HTTPError(405,
                            body=self.get_response(errors=[{
                                'code': '',
                                'message': 'Method not allowed',
                                'detail': err
                            }]))
        except (peewee.IntegrityError, peewee.DataError) as e:
            raise HTTPError(400,
                            body=self.get_response(errors=[{
                                'code': '',
                                'message': 'Invalid parameters',
                                'detail': str(e)
                            }]))
        self.response(result=await self.serialize(item))
示例#3
0
    async def delete(self, item_id):
        """
        Handles DELETE request.

        _delete method must be defined to handle delete logic. If method
        is not defined, HTTP 405 is raised.

        If deletion is finished, writes to response HTTP code 200 and
        a message 'Item deleted'.

        :raises: HTTPError 405 if model object is not deletable.
        """
        # DELETE usually does not have body to validate.
        await self.validate(self.request.body or {},
                            self.delete_schema_input,
                            item_id=item_id)
        item = await self.get_item(item_id)
        try:
            # We can only delete item if model method _delete() is implemented
            await item._delete(self.application)
        except AttributeError as e:
            raise HTTPError(405,
                            body=self.get_response(errors=[{
                                'code': '',
                                'message': 'Method not allowed',
                                'detail': str(e)
                            }]))

        self.response(result='Item deleted')
示例#4
0
    async def get(self):
        """
        Handles GET request.

        1. Validates GET parameters using GET input schema and validator.
        2. Executes query using given query parameters.
        3. Paginates.
        4. Serializes result.
        5. Writes to response, not finishing it.

        :raises: In case of bad query parameters - HTTP 400.
        """
        await self.validate(
            {
                k: self.get_argument(k)
                for k in self.request.query_arguments.keys()
            }, self.get_schema_input)
        try:
            qs = self.get_queryset()
        except AttributeError as e:
            # Wrong field name in filter or order_by
            raise HTTPError(
                400,
                body=self.get_response(errors=[{
                    'code': '',
                    'message': 'Bad query arguments',
                    'detail': xhtml_escape(str(e))
                }]))
        items, pagination = await self._get_items(qs)
        result = []
        for m in items:
            result.append(await self.serialize(m))
        self.response(result={'items': result}, pagination=pagination)
示例#5
0
    async def bad_permissions(self):
        """
        Returns answer of access denied.

        :raises: HTTPError 401
        """
        raise HTTPError(
            401,
            body=self.get_response(errors=[{
                'code': '',
                'message': 'Access denied'
            }]))
示例#6
0
    async def put(self, item_id):
        """
        Handles PUT request.
        Validates data and updates given item.

        Returns serialized model.

        Raises 405 in case of not updatable model (there must be
        _update method implemented in model class).

        Raises 400 in case of violated constraints, invalid parameters and other
        data and integrity errors.

        :raises: HTTP 405, HTTP 400.
        """
        item = await self.get_item(item_id)

        data = await self.validate(self.request.body,
                                   self.put_schema_input,
                                   item_id=item_id)
        try:
            item = await item._update(self.application, data)
        except AttributeError as e:
            # We can only update item if model method _update is implemented
            raise HTTPError(
                405,
                body=self.get_response(errors=[{
                    'code': '',
                    'message': 'Method not allowed',
                    'detail': xhtml_escape(str(e))
                }]))
        except (peewee.IntegrityError, peewee.DataError) as e:
            raise HTTPError(400,
                            body=self.get_response(errors=[{
                                'code': '',
                                'message': 'Invalid parameters',
                                'detail': str(e)
                            }]))

        self.response(result=await self.serialize(item))
示例#7
0
    async def validate(self, data, schema, **kwargs):
        """
        Method to validate parameters.
        Raises HTTPError(400) with error info for invalid data.

        :param data: bytes or dict
        :param schema: dict, valid JSON schema
          (http://json-schema.org/latest/json-schema-validation.html)
        :return: None if data is not valid. Else dict(data)
        """
        # Get and parse arguments
        if isinstance(data, dict):
            _data = data  # pragma: no cover
        else:
            try:
                _data = json.loads(data.decode())
            except ValueError as exc:
                # json.loads error
                raise HTTPError(
                    400,
                    body=self.get_response(errors=[{
                        'code': '',
                        'message': 'Request body is not a valid json object',
                        'detail': str(exc)
                    }]))
        v = validator_for(schema)(schema)
        errors = []
        for error in v.iter_errors(_data):
            # error is an instance of jsonschema.exceptions.ValidationError
            err_msg = xhtml_escape(error.message)
            errors.append({
                'code': '',
                'message': 'Validation failed',
                'detail': err_msg
            })
        if errors:
            # data does not pass validation
            raise HTTPError(400, body=self.get_response(errors=errors))
        return _data
示例#8
0
    async def _get_items(self, qs):
        """
        Gets queryset and paginates it.
        It executes database query. If total amount of items should be
        received (self.total = True), queries are executed in parallel.

        :param qs: peewee queryset
        :return: tuple: executed query, pagination info (dict)
        :raises: In case of bad query parameters - HTTP 400.
        """
        pagination = {'offset': self.offset}
        try:
            if self.total:
                # Execute requests to database in parallel (items + total)
                awaitables = []
                qs_total = self.get_queryset(paginate=False)
                if self.prefetch_queries:
                    # Support of prefetch queries
                    awaitables.append(
                        self.application.objects.prefetch(
                            qs, *self.prefetch_queries))
                else:
                    awaitables.append(self.application.objects.execute(qs))
                awaitables.append(self.application.objects.count(qs_total))
                items, total = await multi(awaitables)
                # Set total items number
                pagination['total'] = total
            else:
                if self.prefetch_queries:
                    items = await self.application.objects.prefetch(
                        qs, *self.prefetch_queries)
                else:
                    items = await self.application.objects.execute(qs)
        except (peewee.DataError, ValueError) as e:
            # Bad parameters
            raise HTTPError(400,
                            body=self.get_response(errors=[{
                                'code': '',
                                'message': 'Bad query arguments',
                                'detail': str(e)
                            }]))
        # Set number of fetched items
        pagination['limit'] = len(items)  # TODO WTF? Why limit is set?

        return items, pagination
示例#9
0
    async def get_item(self, item_id):
        """
        Fetches item from database by PK.
        Result is cached in self._instance for multiple calls

        :raises: HTTP 404 if no item found.
        :returns: raw object if exists.
        :rtype: ORM model instance.
        """
        if not self._instance:
            try:
                self._instance = await self.application.objects.get(
                    self.get_queryset(item_id))
            except (self.model_cls.DoesNotExist, ValueError) as e:
                raise HTTPError(404,
                                body=self.get_response(errors=[{
                                    'code': '',
                                    'message': 'Item not found',
                                    'detail': str(e)
                                }]))
        return self._instance